Skip to content

Commit 55ab9ba

Browse files
killaguclaude
andauthored
Merge commit from fork
When a malformed Host header containing @ symbol (e.g., "evil.com:fake@legitimate.com") is received, use URL parser to correctly extract the actual host portion instead of naively splitting by colon which would return attacker-controlled value. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fecd464 commit 55ab9ba

File tree

3 files changed

+83
-1
lines changed

3 files changed

+83
-1
lines changed

__tests__/request/host.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,48 @@ describe('req.host', () => {
9494
})
9595
})
9696
})
97+
98+
describe('with Host header containing @', () => {
99+
it('should correctly parse host from userinfo@host format', () => {
100+
const req = request()
101+
req.header.host = 'evil.com:fake@legitimate.com'
102+
assert.strictEqual(req.host, 'legitimate.com')
103+
})
104+
105+
it('should correctly parse host from user@host format', () => {
106+
const req = request()
107+
req.header.host = 'user@example.com'
108+
assert.strictEqual(req.host, 'example.com')
109+
})
110+
111+
it('should correctly parse host with port from userinfo@host:port format', () => {
112+
const req = request()
113+
req.header.host = 'user:pass@example.com:8080'
114+
assert.strictEqual(req.host, 'example.com:8080')
115+
})
116+
117+
it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {
118+
const req = request()
119+
req.app.proxy = true
120+
req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'
121+
req.header.host = 'foo.com'
122+
assert.strictEqual(req.host, 'legitimate.com')
123+
})
124+
125+
it('should correctly parse @ in :authority on HTTP/2', () => {
126+
const req = request({
127+
httpVersionMajor: 2,
128+
httpVersion: '2.0'
129+
})
130+
req.header[':authority'] = 'evil.com:fake@legitimate.com'
131+
req.header.host = 'foo.com'
132+
assert.strictEqual(req.host, 'legitimate.com')
133+
})
134+
135+
it('should return empty string for invalid host with @', () => {
136+
const req = request()
137+
req.header.host = 'user@'
138+
assert.strictEqual(req.host, '')
139+
})
140+
})
97141
})

__tests__/request/hostname.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,32 @@ describe('req.hostname', () => {
7070
})
7171
})
7272
})
73+
74+
describe('with Host header containing @', () => {
75+
it('should correctly parse hostname from userinfo@host format', () => {
76+
const req = request()
77+
req.header.host = 'evil.com:fake@legitimate.com'
78+
assert.strictEqual(req.hostname, 'legitimate.com')
79+
})
80+
81+
it('should correctly parse hostname from user@host format', () => {
82+
const req = request()
83+
req.header.host = 'user@example.com'
84+
assert.strictEqual(req.hostname, 'example.com')
85+
})
86+
87+
it('should correctly parse hostname with port from userinfo@host:port format', () => {
88+
const req = request()
89+
req.header.host = 'user:pass@example.com:8080'
90+
assert.strictEqual(req.hostname, 'example.com')
91+
})
92+
93+
it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {
94+
const req = request()
95+
req.app.proxy = true
96+
req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'
97+
req.header.host = 'foo.com'
98+
assert.strictEqual(req.hostname, 'legitimate.com')
99+
})
100+
})
73101
})

lib/request.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,17 @@ module.exports = {
256256
if (!host) host = this.get('Host')
257257
}
258258
if (!host) return ''
259-
return splitCommaSeparatedValues(host, 1)[0]
259+
host = splitCommaSeparatedValues(host, 1)[0]
260+
// Host header may contain userinfo (e.g., "user@host") which is invalid per RFC 7230.
261+
// Use URL parser to correctly extract the host portion.
262+
if (host.includes('@')) {
263+
try {
264+
host = new URL(`http://${host}`).host
265+
} catch (e) {
266+
return ''
267+
}
268+
}
269+
return host
260270
},
261271

262272
/**

0 commit comments

Comments
 (0)