diff --git a/add-on/src/lib/ipfs-proxy/api-whitelist.json b/add-on/src/lib/ipfs-proxy/api-whitelist.json new file mode 100644 index 000000000..c8680fbd4 --- /dev/null +++ b/add-on/src/lib/ipfs-proxy/api-whitelist.json @@ -0,0 +1,20 @@ +[ + "add", + "block", + "cat", + "dag", + "dht", + "dns", + "files", + "get", + "id", + "ls", + "name.resolve", + "object", + "pin", + "pubsub", + "repo", + "stats", + "swarm", + "version" +] diff --git a/add-on/src/lib/ipfs-proxy/index.js b/add-on/src/lib/ipfs-proxy/index.js index 91dc081dd..423304949 100644 --- a/add-on/src/lib/ipfs-proxy/index.js +++ b/add-on/src/lib/ipfs-proxy/index.js @@ -4,6 +4,7 @@ const browser = require('webextension-polyfill') const { createProxyServer, closeProxyServer } = require('ipfs-postmsg-proxy') const AccessControl = require('./access-control') +const createPreApiWhitelist = require('./pre-api-whitelist') const createPreAcl = require('./pre-acl') const createPreMfsScope = require('./pre-mfs-scope') const createRequestAccess = require('./request-access') @@ -33,6 +34,7 @@ function createIpfsProxy (getIpfs, getState) { postMessage: (data) => port.postMessage(data), getMessageData: (d) => d, pre: (fnName) => [ + createPreApiWhitelist(fnName), createPreAcl(fnName, getState, getScope, accessControl, requestAccess), createPreMfsScope(fnName, getScope, getIpfs) ] diff --git a/add-on/src/lib/ipfs-proxy/pre-api-whitelist.js b/add-on/src/lib/ipfs-proxy/pre-api-whitelist.js new file mode 100644 index 000000000..816e985bb --- /dev/null +++ b/add-on/src/lib/ipfs-proxy/pre-api-whitelist.js @@ -0,0 +1,24 @@ +// Some APIs are too sensitive to be exposed to dapps on every website +// We follow a safe security practice of denying everything and allowing access +// to a pre-approved list of known APIs. This way if a new API is added +// it will be blocked by default, until it is explicitly enabled below. +const API_WHITELIST = Object.freeze(require('./api-whitelist.json')) + +// Creates a "pre" function that is called prior to calling a real function +// on the IPFS instance. It will throw if access is denied due to API not being whitelisted +function createPreApiWhitelist (permission) { + return async (...args) => { + // Fail fast if API or namespace is not explicitly whitelisted + const permRoot = permission.split('.')[0] + if (!(API_WHITELIST.includes(permRoot) || API_WHITELIST.includes(permission))) { + console.log(`[ipfs-companion] Access to ${permission} API over window.ipfs is blocked. If you feel it should be allowed, open an issue at https://github.com/ipfs-shipyard/ipfs-companion/issues/new`) + const err = new Error(`Access to ${permission} API is globally blocked for window.ipfs`) + err.output = { payload: { isIpfsProxyWhitelistError: true, permission } } + throw err + } + + return args + } +} + +module.exports = createPreApiWhitelist diff --git a/test/functional/lib/ipfs-proxy/pre-api-whitelist.test.js b/test/functional/lib/ipfs-proxy/pre-api-whitelist.test.js new file mode 100644 index 000000000..5966538d5 --- /dev/null +++ b/test/functional/lib/ipfs-proxy/pre-api-whitelist.test.js @@ -0,0 +1,30 @@ +'use strict' +const { describe, it, before, after } = require('mocha') +const { expect } = require('chai') +const { URL } = require('url') +const createPreApiWhitelist = require('../../../../add-on/src/lib/ipfs-proxy/pre-api-whitelist') + +describe('lib/ipfs-proxy/pre-api-whitelist', () => { + before(() => { + global.URL = URL + }) + + it('should throw early if access was not enabled via global API whitelist', async () => { + const permission = 'config.show' + const preApiWhitelist = createPreApiWhitelist(permission) + + let error + + try { + await preApiWhitelist() + } catch (err) { + error = err + } + + expect(() => { if (error) throw error }).to.throw(`Access to ${permission} API is globally blocked for window.ipfs`) + }) + + after(() => { + delete global.URL + }) +})