Default error handler throws ERR_INVALID_URL on an unparsable Host header
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 11.2k
- Forks
- 899
- Avg merge
- 2d 24m
- Merged PRs (30d)
- 40
Description
Environment
- nitropack: 2.13.4 (
latest) - h3: 1.15.11
- Node.js: 22.17.0
- preset:
node-server - Reproduced on Windows 11. The same error also appears on a Linux production deployment.
Reproduction
https://github.com/sergiooak/nitro-host-header-error-handler
npm install
npm run build
node .output/server/index.mjs
# then, in another shell:
curl -s -H 'Host: a b' http://127.0.0.1:3000/x
Describe the bug
The default error handler builds the request URL from the raw Host header. When the header is not a valid URL authority, new URL() throws inside the handler, and the throw is not caught there.
Flow:
- A request arrives with a
Hostheader thatnew URL()cannot parse, for example a value containing a space. - The route does not match, so Nitro runs the default error handler to render the 404.
defaultHandlercallsgetRequestURL(event, { xForwardedHost: true, xForwardedProto: true }).getRequestURLrunsnew URL(path, \${protocol}://${host}`)with the rawHost.new URL()throwsTypeError [ERR_INVALID_URL]`.- The throw escapes the error handler as a rejected promise.
trapUnhandledNodeErrorscatches it and logs the full stack. The client gets a 404 with a reduced body. The process stays up.
Every request with such a Host logs one full stack trace.
Root cause:
- h3
getRequestURLbuilds the URL from the unvalidatedHostheader (getRequestHostreturnsevent.node.req.headers.host). - Nitro
defaultHandler(src/runtime/internal/error/prod.ts) callsgetRequestURLwith notry/catch, so a throw in the error handler is not contained.
Additional context
Which Host values trigger it: new URL('http://' + host) throws for authority values containing characters such as a space, [, ], <, %, or ::. It does not throw for " or /. Vulnerability scanners commonly send values like "></A></ADDRESS>"><script>alert(document.domain)</script><ADDRESS><A "/xss"', which throws because of the space.
Suggested fix: wrap the getRequestURL call in defaultHandler in try/catch and fall back to event.path, or validate the host in getRequestURL. The same class of bug was handled in @hono/node-server (CVE-2024-32652).
Production note: on a node-server deployment behind PM2, under sustained scanning we saw the service degrade until it stopped responding. A plain pm2 restart did not recover it; only pm2 delete plus a fresh start did. We have not isolated that exact mechanism in the minimal reproduction, which only shows the throw, the logged unhandled rejection, and the degraded response, with the process staying up. We are flagging it because an unauthenticated request reaches the error handler and every hit logs a full stack trace, which compounds at scanner volume.
Logs
TypeError: Invalid URL
at new URL (node:internal/url:818:25)
at getRequestURL (.output/server/chunks/_/nitro.mjs:763:10)
at defaultHandler (.output/server/chunks/_/nitro.mjs:4117:15)
at defaultNitroErrorHandler (.output/server/chunks/_/nitro.mjs:4107:17)
at errorHandler (.output/server/chunks/_/nitro.mjs:4171:13)
at Object.onError (.output/server/chunks/_/nitro.mjs:4407:14)
at Server.toNodeHandle (.output/server/chunks/_/nitro.mjs:1847:27)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
code: 'ERR_INVALID_URL',
input: '/x',
base: 'http://a b'
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Inspect src/runtime/internal/error/prod.ts and the defaultHandler call to getRequestURL; use the linked reproduction to confirm the failure with curl and the malformed Host header. Contain the URL-parsing failure using the suggested fallback, then rerun the reproduction and verify that the 404 does not emit an ERR_INVALID_URL stack trace.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100