Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 3e67f0a

Browse files
achingbrainvasco-santoslidel
authored
fix: reject requests when cors origin list is empty (#3674) (#3676)
* fix: reject requests when cors origin list is empty (#3674) If CORS origin list is empty, Hapi throws an error as it considers that to be invalid configuration. We want to reject requests that send and origin or a referer when no allowed origins have been configured, so when these headers are sent, reject the request if no allowed origins are present in the config. Co-authored-by: Vasco Santos <[email protected]> Co-authored-by: Marcin Rataj <[email protected]> * chore: ts-ignore until #3655 lands Co-authored-by: Vasco Santos <[email protected]> Co-authored-by: Marcin Rataj <[email protected]>
1 parent 511147b commit 3e67f0a

File tree

4 files changed

+152
-3
lines changed

4 files changed

+152
-3
lines changed

packages/ipfs-grpc-client/src/utils/load-services.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const CONVERSION_OPTS = {
2121
* @returns {object}
2222
*/
2323
module.exports = function loadServices () {
24+
// @ts-ignore protobuf's generated types are incompatible with it's own types
2425
const root = protobuf.Root.fromJSON(protocol)
2526
const output = {}
2627

packages/ipfs-grpc-server/src/utils/load-services.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const CONVERSION_OPTS = {
1414
}
1515

1616
module.exports = function loadServices () {
17+
// @ts-ignore protobuf's generated types are incompatible with it's own types
1718
const root = protobuf.Root.fromJSON(protocol)
1819
const output = {}
1920

packages/ipfs-http-server/src/index.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,36 @@ async function serverCreator (serverAddrs, createServer, ipfs, cors) {
4040
return servers
4141
}
4242

43+
/**
44+
* @param {string} [str]
45+
* @param {string[]} [allowedOrigins]
46+
*/
47+
function isAllowedOrigin (str, allowedOrigins = []) {
48+
if (!str) {
49+
return false
50+
}
51+
52+
let origin
53+
54+
try {
55+
origin = (new URL(str)).origin
56+
} catch {
57+
return false
58+
}
59+
60+
for (const allowedOrigin of allowedOrigins) {
61+
if (allowedOrigin === '*') {
62+
return true
63+
}
64+
65+
if (allowedOrigin === origin) {
66+
return true
67+
}
68+
}
69+
70+
return false
71+
}
72+
4373
class HttpApi {
4474
constructor (ipfs, options = {}) {
4575
this._ipfs = ipfs
@@ -150,11 +180,17 @@ class HttpApi {
150180

151181
const headers = request.headers || {}
152182
const origin = headers.origin || ''
153-
const referrer = headers.referrer || ''
183+
const referer = headers.referer || ''
154184
const userAgent = headers['user-agent'] || ''
155185

156-
// If these are set, we leave up to CORS and CSRF checks.
157-
if (origin || referrer) {
186+
// If these are set, check them against the configured list
187+
if (origin || referer) {
188+
if (!isAllowedOrigin(origin || referer, cors.origin)) {
189+
// Hapi will not allow an empty CORS origin list so we have to manually
190+
// reject the request if CORS origins have not been configured
191+
throw Boom.forbidden()
192+
}
193+
158194
return h.continue
159195
}
160196

packages/ipfs-http-server/test/cors.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,23 @@ describe('cors', () => {
3232
}
3333
}, { ipfs, cors: { origin: [origin] } })
3434

35+
expect(res).to.have.property('statusCode', 200)
3536
expect(res).to.have.nested.property('headers.access-control-allow-origin', origin)
3637
})
3738

39+
it('allows request when referer is supplied in request', async () => {
40+
const origin = 'http://localhost:8080'
41+
const res = await http({
42+
method: 'POST',
43+
url: '/api/v0/id',
44+
headers: {
45+
referer: origin + '/index.html'
46+
}
47+
}, { ipfs, cors: { origin: [origin] } })
48+
49+
expect(res).to.have.property('statusCode', 200)
50+
})
51+
3852
it('does not allow credentials when omitted in config', async () => {
3953
const origin = 'http://localhost:8080'
4054
const res = await http({
@@ -149,5 +163,102 @@ describe('cors', () => {
149163

150164
expect(res).to.have.property('statusCode', 404)
151165
})
166+
167+
it('rejects requests when cors origin list is empty and origin is sent', async () => {
168+
const origin = 'http://localhost:8080'
169+
const res = await http({
170+
method: 'POST',
171+
url: '/api/v0/id',
172+
headers: {
173+
origin
174+
}
175+
}, {
176+
ipfs,
177+
cors: { origin: [] }
178+
})
179+
180+
expect(res).to.have.property('statusCode', 403)
181+
})
182+
183+
it('rejects requests when cors origin list is empty and referer is sent', async () => {
184+
const referer = 'http://localhost:8080/index.html'
185+
const res = await http({
186+
method: 'POST',
187+
url: '/api/v0/id',
188+
headers: {
189+
referer
190+
}
191+
}, {
192+
ipfs,
193+
cors: { origin: [] }
194+
})
195+
196+
expect(res).to.have.property('statusCode', 403)
197+
})
198+
199+
it('rejects requests when cors origin list is empty and referer and origin are sent', async () => {
200+
const referer = 'http://localhost:8080/index.html'
201+
const res = await http({
202+
method: 'POST',
203+
url: '/api/v0/id',
204+
headers: {
205+
referer,
206+
origin: 'http://localhost:8080'
207+
}
208+
}, {
209+
ipfs,
210+
cors: { origin: [] }
211+
})
212+
213+
expect(res).to.have.property('statusCode', 403)
214+
})
215+
216+
it('rejects requests when cors origin list is empty and origin is sent as "null" (e.g. data urls and sandboxed iframes)', async () => {
217+
const origin = 'null'
218+
const res = await http({
219+
method: 'POST',
220+
url: '/api/v0/id',
221+
headers: {
222+
origin
223+
}
224+
}, {
225+
ipfs,
226+
cors: { origin: [] }
227+
})
228+
229+
expect(res).to.have.property('statusCode', 403)
230+
})
231+
232+
it('rejects requests when cors origin list does not contain the correct origin and origin is sent', async () => {
233+
const origin = 'http://localhost:8080'
234+
const res = await http({
235+
method: 'POST',
236+
url: '/api/v0/id',
237+
headers: {
238+
origin
239+
}
240+
}, {
241+
ipfs,
242+
cors: { origin: ['http://example.com:8080'] }
243+
})
244+
245+
expect(res).to.have.property('statusCode', 403)
246+
})
247+
248+
it('rejects requests when cors origin list does not contain the correct origin and referer is sent', async () => {
249+
const referer = 'http://localhost:8080/index.html'
250+
const res = await http({
251+
method: 'POST',
252+
url: '/api/v0/id',
253+
headers: {
254+
referer
255+
}
256+
}, {
257+
ipfs,
258+
cors: { origin: ['http://example.com:8080'] }
259+
})
260+
261+
expect(res).to.have.property('statusCode', 403)
262+
})
152263
})
153264
})

0 commit comments

Comments
 (0)