Skip to content

Commit 78ce18d

Browse files
committed
fix: false-positives in sameGateway
This ensures sameGateway returns true only if the tested URL is either a valid path, subdomain, or RPC URL related to local Gateway or Kubo instance. Everything else should be ignored, because people may run Kubo Gateway on localhost:8080, and then stop it, and then run some unrelated HTTP server on the same port. This is unfortunate papercut due to the 8080 being very very popular default port for all things HTTP. Luckily, Companion has enough information to make this right.
1 parent 74cb934 commit 78ce18d

File tree

4 files changed

+33
-13
lines changed

4 files changed

+33
-13
lines changed

add-on/src/lib/ipfs-path.js

+5
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ export function sameGateway (url, gwUrl) {
126126
url.hostname = '127.0.0.1'
127127
}
128128

129+
// Additional check to avoid false-positives when user has some unrelated HTTP server running on localhost:8080
130+
// It is not "sameGateway" if "localhost" URL does not look like Gateway or RPC URL.
131+
// This removes surface for bugs like https://github.com/ipfs/ipfs-companion/issues/1162
132+
if (!(isIPFS.url(url.toString()) || isIPFS.subdomain(url.toString()) || url.pathname.startsWith('/api/v0/') || url.pathname.startsWith('/webui'))) return false
133+
129134
const gws = [gwUrl.host]
130135

131136
// localhost gateway has more than one hostname

add-on/src/lib/ipfs-request.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,7 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
144144

145145
// When local IPFS node is unreachable , show recovery page where user can redirect
146146
// to public gateway.
147-
if (!state.nodeActive && // node is not active
148-
!state.redirect && // this is not a redirect request
149-
request.type === 'main_frame' && // this is a request for a root document
150-
sameGateway(request.url, state.gwURL) // this is a request to the local gateway
151-
) {
147+
if (!state.nodeActive && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
152148
const publicUri = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
153149
return { redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}` }
154150
}

test/functional/lib/ipfs-path.test.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,23 @@ describe('ipfs-path.js', function () {
184184
const gw = 'http://localhost:8080'
185185
expect(sameGateway(url, gw)).to.equal(true)
186186
})
187-
it('should return true on 127.0.0.1/0.0.0.0 host match', function () {
187+
it('should return true on localhost subdomain host match', function () {
188+
const url = 'http://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.ipfs.localhost:8080/foo/bar'
189+
const gw = 'http://localhost:8080'
190+
expect(sameGateway(url, gw)).to.equal(true)
191+
})
192+
it('should return true on RPC webui path 127.0.0.1/0.0.0.0 host match', function () {
193+
// this is misconfiguration, but handling it in sameGateway ensures users who do this dont waste our time debugging
188194
const url = 'http://0.0.0.0:5001/webui'
189195
const api = 'http://127.0.0.1:5001'
190196
expect(sameGateway(url, api)).to.equal(true)
191197
})
198+
it('should return true on RPC /api/v0 path 127.0.0.1/0.0.0.0 host match', function () {
199+
// this is misconfiguration, but handling it in sameGateway ensures users who do this dont waste our time debugging
200+
const url = 'http://0.0.0.0:5001/api/v0/id'
201+
const api = 'http://127.0.0.1:5001'
202+
expect(sameGateway(url, api)).to.equal(true)
203+
})
192204
it('should return false on hostname match but different port', function () {
193205
const url = 'https://localhost:8081/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
194206
const gw = 'http://localhost:8080'
@@ -289,7 +301,7 @@ describe('ipfs-path.js', function () {
289301
expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
290302
})
291303
it('should return false for /ipfs/ URL at Local Gateway', function () {
292-
const url = `${state.gwURL}/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
304+
const url = `${state.gwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
293305
expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(false)
294306
})
295307
it('should return false for IPFS content loaded from IPFS API port', function () {

test/functional/lib/ipfs-request-gateway-redirect.test.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ describe('modifyRequest.onBeforeRequest:', function () {
353353
describe('request for IPFS path at the localhost', function () {
354354
// we do not touch local requests, as it may interfere with other nodes running at the same machine
355355
// or could produce false-positives such as redirection from localhost:5001/ipfs/path to localhost:8080/ipfs/path
356-
it('should fix localhost API hostname to IP', function () {
356+
it('should fix localhost Kubo RPC hostname to IP', function () {
357357
const request = url2request('http://localhost:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
358358
// expectNoRedirect(modifyRequest, request)
359359
expect(modifyRequest.onBeforeRequest(request).redirectUrl)
@@ -375,14 +375,14 @@ describe('modifyRequest.onBeforeRequest:', function () {
375375
expect(modifyRequest.onBeforeRequest(request).redirectUrl)
376376
.to.equal('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
377377
})
378-
it('should fix localhost API to IP', function () {
378+
it('should be left untouched if /webui on localhost Kubo RPC port', function () {
379379
// https://github.com/ipfs/ipfs-companion/issues/291
380380
const request = url2request('http://localhost:5001/webui')
381381
// expectNoRedirect(modifyRequest, request)
382382
expect(modifyRequest.onBeforeRequest(request).redirectUrl)
383383
.to.equal('http://127.0.0.1:5001/webui')
384384
})
385-
it('should be left untouched if localhost API IP is used, even when x-ipfs-path is present', function () {
385+
it('should be left untouched if localhost Kubo RPC IP is used, even when x-ipfs-path is present', function () {
386386
// https://github.com/ipfs-shipyard/ipfs-companion/issues/604
387387
const request = url2request('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
388388
request.responseHeaders = [{ name: 'X-Ipfs-Path', value: '/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DDIFF' }]
@@ -431,21 +431,28 @@ describe('modifyRequest.onBeforeRequest:', function () {
431431
global.browser = browser
432432
state.ipfsNodeType = 'external'
433433
state.redirect = true
434-
state.peerCount = -1
434+
state.peerCount = -1 // this simulates Kubo RPC being offline
435435
state.gwURLString = 'http://localhost:8080'
436436
state.gwURL = new URL('http://localhost:8080')
437437
state.pubGwURLString = 'https://ipfs.io'
438438
state.pubGwURL = new URL('https://ipfs.io')
439-
state.redirect = false
440439
})
441440
it('should present recovery page if node is offline', function () {
442441
expect(state.nodeActive).to.be.equal(false)
443442
const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
444443
expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('chrome-extension://testid/dist/recovery/recovery.html#https%3A%2F%2Fipfs.io%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%2Ffoo%2Fbar')
445444
})
446-
it('should not block requests when the request is redirecting', function () {
445+
it('should not show recovery page if node is offline, redirect is enabled, but non-gateway URL failed to load from the same port', function () {
446+
// this covers https://github.com/ipfs/ipfs-companion/issues/1162 and https://twitter.com/unicomp21/status/1626244123102679041
447447
state.redirect = true
448448
expect(state.nodeActive).to.be.equal(false)
449+
const request = url2request('https://localhost:8080/')
450+
expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
451+
})
452+
it('should not show recovery page if extension is disabled', function () {
453+
// allows user to quickly avoid anything similar to https://github.com/ipfs/ipfs-companion/issues/1162
454+
state.active = false
455+
expect(state.nodeActive).to.be.equal(false)
449456
const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
450457
expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
451458
})

0 commit comments

Comments
 (0)