Skip to content

Commit f9f6416

Browse files
committed
fix: strip sensitive headers on redirect to different origin
1 parent 9dd0687 commit f9f6416

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

lib/eventsource.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ function hasBom (buf) {
3131
**/
3232
function EventSource (url, eventSourceInitDict) {
3333
var readyState = EventSource.CONNECTING
34+
var headers = eventSourceInitDict && eventSourceInitDict.headers
35+
var hasNewOrigin = false
3436
Object.defineProperty(this, 'readyState', {
3537
get: function () {
3638
return readyState
@@ -52,11 +54,12 @@ function EventSource (url, eventSourceInitDict) {
5254
readyState = EventSource.CONNECTING
5355
_emit('error', new Event('error', {message: message}))
5456

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

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

7780
var discardTrailingNewline = false
@@ -85,9 +88,10 @@ function EventSource (url, eventSourceInitDict) {
8588
var isSecure = options.protocol === 'https:'
8689
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }
8790
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId
88-
if (eventSourceInitDict && eventSourceInitDict.headers) {
89-
for (var i in eventSourceInitDict.headers) {
90-
var header = eventSourceInitDict.headers[i]
91+
if (headers) {
92+
var reqHeaders = hasNewOrigin ? removeUnsafeHeaders(headers) : headers
93+
for (var i in reqHeaders) {
94+
var header = reqHeaders[i]
9195
if (header) {
9296
options.headers[i] = header
9397
}
@@ -147,13 +151,17 @@ function EventSource (url, eventSourceInitDict) {
147151

148152
// Handle HTTP redirects
149153
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
150-
if (!res.headers.location) {
154+
var location = res.headers.location
155+
if (!location) {
151156
// Server sent redirect response without Location header.
152157
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}))
153158
return
154159
}
160+
var prevOrigin = original(url)
161+
var nextOrigin = original(location)
162+
hasNewOrigin = prevOrigin !== nextOrigin
155163
if (res.statusCode === 307) reconnectUrl = url
156-
url = res.headers.location
164+
url = location
157165
process.nextTick(connect)
158166
return
159167
}
@@ -443,3 +451,23 @@ function MessageEvent (type, eventInitDict) {
443451
}
444452
}
445453
}
454+
455+
/**
456+
* Returns a new object of headers that does not include any authorization and cookie headers
457+
*
458+
* @param {Object} headers An object of headers ({[headerName]: headerValue})
459+
* @return {Object} a new object of headers
460+
* @api private
461+
*/
462+
function removeUnsafeHeaders (headers) {
463+
var safe = {}
464+
for (var key in headers) {
465+
if (/^(cookie|authorization)$/i.test(key)) {
466+
continue
467+
}
468+
469+
safe[key] = headers[key]
470+
}
471+
472+
return safe
473+
}

test/eventsource_test.js

Lines changed: 43 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy