mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 13:35:04 +02:00
@@ -98,6 +98,16 @@ describe('isBlockedHost', () => {
|
||||
it('handles bracketed and trailing-dot hostnames', () => {
|
||||
expect(isBlockedHost('[::1]')).toBe(true)
|
||||
expect(isBlockedHost('localhost.')).toBe(true)
|
||||
expect(isBlockedHost('localhost...')).toBe(true)
|
||||
expect(isBlockedHost('127.0.0.1.')).toBe(true)
|
||||
expect(isBlockedHost('example.com.')).toBe(false)
|
||||
})
|
||||
|
||||
it('strips trailing dots in linear time on hostile input (ReDoS regression)', () => {
|
||||
const hostile = '.'.repeat(100000) + 'x'
|
||||
const start = Date.now()
|
||||
expect(isBlockedHost(hostile)).toBe(false)
|
||||
expect(Date.now() - start).toBeLessThan(1000)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -62,9 +62,14 @@ const DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:']
|
||||
function normalizeHostnameForChecks (hostname: string): string {
|
||||
// URL.hostname is already punycode-normalized by WHATWG URL for IDNs.
|
||||
// Keep it lowercase for comparisons.
|
||||
const trimmed = hostname.trim().toLowerCase().replace(/\.+$/, '')
|
||||
if (trimmed.startsWith('[') && trimmed.endsWith(']')) return trimmed.slice(1, -1)
|
||||
return trimmed
|
||||
let host = hostname.trim().toLowerCase()
|
||||
// Strip trailing dots with a linear scan rather than /\.+$/, which backtracks
|
||||
// in polynomial time on hostile input (e.g. many '.' followed by a non-dot).
|
||||
let end = host.length
|
||||
while (end > 0 && host.charCodeAt(end - 1) === 0x2e /* '.' */) end--
|
||||
host = host.slice(0, end)
|
||||
if (host.startsWith('[') && host.endsWith(']')) return host.slice(1, -1)
|
||||
return host
|
||||
}
|
||||
|
||||
// Expands an IPv6 literal (possibly compressed, zone-suffixed, or carrying an
|
||||
|
||||
@@ -688,8 +688,11 @@ export function start (
|
||||
return cookie !== undefined ? { lookup, headers: { Cookie: cookie } } : { lookup }
|
||||
} catch (err) {
|
||||
if (err instanceof SsrfError) {
|
||||
// Log the detail (incl. the offending url) server-side, but never reflect it
|
||||
// back: res.send(string) is served as text/html, so echoing err.message would
|
||||
// be a reflected-XSS / info-leak sink. err.code is a fixed enum, safe to return.
|
||||
ctx.warn('import blocked', { code: err.code, url })
|
||||
res.status(400).send(err.message)
|
||||
res.status(400).send(`Import URL rejected: ${err.code}`)
|
||||
return undefined
|
||||
}
|
||||
throw err
|
||||
|
||||
Reference in New Issue
Block a user