Skip to content

Commit 10ee0c4

Browse files
committed
fix: strip sensitive headers on redirect to different origin
1 parent a95ba90 commit 10ee0c4

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

lib/eventsource.js

+38-10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ function hasBom (buf) {
3232
**/
3333
function EventSource (url, eventSourceInitDict) {
3434
var readyState = EventSource.CONNECTING
35+
var headers = eventSourceInitDict && eventSourceInitDict.headers
36+
var hasNewOrigin = false
3537
Object.defineProperty(this, 'readyState', {
3638
get: function () {
3739
return readyState
@@ -53,11 +55,12 @@ function EventSource (url, eventSourceInitDict) {
5355
readyState = EventSource.CONNECTING
5456
_emit('error', new Event('error', {message: message}))
5557

56-
// The url may have been changed by a temporary
57-
// redirect. If that's the case, revert it now.
58+
// The url may have been changed by a temporary redirect. If that's the case,
59+
// revert it now, and flag that we are no longer pointing to a new origin
5860
if (reconnectUrl) {
5961
url = reconnectUrl
6062
reconnectUrl = null
63+
hasNewOrigin = false
6164
}
6265
setTimeout(function () {
6366
if (readyState !== EventSource.CONNECTING || self.connectionInProgress) {
@@ -70,9 +73,9 @@ function EventSource (url, eventSourceInitDict) {
7073

7174
var req
7275
var lastEventId = ''
73-
if (eventSourceInitDict && eventSourceInitDict.headers && eventSourceInitDict.headers['Last-Event-ID']) {
74-
lastEventId = eventSourceInitDict.headers['Last-Event-ID']
75-
delete eventSourceInitDict.headers['Last-Event-ID']
76+
if (headers && headers['Last-Event-ID']) {
77+
lastEventId = headers['Last-Event-ID']
78+
delete headers['Last-Event-ID']
7679
}
7780

7881
var discardTrailingNewline = false
@@ -86,9 +89,10 @@ function EventSource (url, eventSourceInitDict) {
8689
var isSecure = options.protocol === 'https:'
8790
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }
8891
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId
89-
if (eventSourceInitDict && eventSourceInitDict.headers) {
90-
for (var i in eventSourceInitDict.headers) {
91-
var header = eventSourceInitDict.headers[i]
92+
if (headers) {
93+
var reqHeaders = hasNewOrigin ? removeUnsafeHeaders(headers) : headers
94+
for (var i in reqHeaders) {
95+
var header = reqHeaders[i]
9296
if (header) {
9397
options.headers[i] = header
9498
}
@@ -148,13 +152,17 @@ function EventSource (url, eventSourceInitDict) {
148152

149153
// Handle HTTP redirects
150154
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
151-
if (!res.headers.location) {
155+
var location = res.headers.location
156+
if (!location) {
152157
// Server sent redirect response without Location header.
153158
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}))
154159
return
155160
}
161+
var prevOrigin = new URL(url).origin
162+
var nextOrigin = new URL(location).origin
163+
hasNewOrigin = prevOrigin !== nextOrigin
156164
if (res.statusCode === 307) reconnectUrl = url
157-
url = res.headers.location
165+
url = location
158166
process.nextTick(connect)
159167
return
160168
}
@@ -463,3 +471,23 @@ function MessageEvent (type, eventInitDict) {
463471
}
464472
}
465473
}
474+
475+
/**
476+
* Returns a new object of headers that does not include any authorization and cookie headers
477+
*
478+
* @param {Object} headers An object of headers ({[headerName]: headerValue})
479+
* @return {Object} a new object of headers
480+
* @api private
481+
*/
482+
function removeUnsafeHeaders (headers) {
483+
var safe = {}
484+
for (var key in headers) {
485+
if (/^(cookie|authorization)$/i.test(key)) {
486+
continue
487+
}
488+
489+
safe[key] = headers[key]
490+
}
491+
492+
return safe
493+
}

test/eventsource_test.js

+43
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,49 @@ describe('HTTP Request', function () {
581581
})
582582
})
583583

584+
it('follows http ' + status + ' redirects, drops sensitive headers on origin change', function (done) {
585+
var redirectSuffix = '/foobar'
586+
var clientRequestedRedirectUrl = false
587+
var receivedHeaders = {}
588+
createServer(function (err, server) {
589+
if (err) return done(err)
590+
591+
var newServerUrl = server.url.replace('http://localhost', 'http://127.0.0.1')
592+
593+
server.on('request', function (req, res) {
594+
if (req.url === '/') {
595+
res.writeHead(status, {
596+
'Connection': 'Close',
597+
'Location': newServerUrl + redirectSuffix
598+
})
599+
res.end()
600+
} else if (req.url === redirectSuffix) {
601+
clientRequestedRedirectUrl = true
602+
receivedHeaders = req.headers
603+
res.writeHead(200, {'Content-Type': 'text/event-stream'})
604+
res.end()
605+
}
606+
})
607+
608+
var es = new EventSource(server.url, {
609+
headers: {
610+
keep: 'me',
611+
authorization: 'Bearer someToken',
612+
cookie: 'some-cookie=yep'
613+
}
614+
})
615+
616+
es.onopen = function () {
617+
assert.ok(clientRequestedRedirectUrl)
618+
assert.equal(newServerUrl + redirectSuffix, es.url)
619+
assert.equal(receivedHeaders.keep, 'me', 'safe header no longer present')
620+
assert.equal(typeof receivedHeaders.authorization, 'undefined', 'authorization header still present')
621+
assert.equal(typeof receivedHeaders.cookie, 'undefined', 'cookie header still present')
622+
server.close(done)
623+
}
624+
})
625+
})
626+
584627
it('causes error event when response is ' + status + ' with missing location', function (done) {
585628
createServer(function (err, server) {
586629
if (err) return done(err)

0 commit comments

Comments
 (0)