Skip to content

http: validate headers in writeEarlyHints#61897

Merged
nodejs-github-bot merged 2 commits intonodejs:mainfrom
rsclarke:fix/earlyhints-crlf
Mar 2, 2026
Merged

http: validate headers in writeEarlyHints#61897
nodejs-github-bot merged 2 commits intonodejs:mainfrom
rsclarke:fix/earlyhints-crlf

Conversation

@rsclarke
Copy link
Contributor

writeEarlyHints() constructs the 103 Early Hints response by directly concatenating user-supplied header names and values into the raw HTTP response. Unlike setHeader(), appendHeader(), and writeHead(), it never calls validateHeaderName() or validateHeaderValue(), allowing CRLF sequences to pass through unchecked.

This means user supplied data passed to writeEarlyHints() can inject arbitrary HTTP response headers (or entire responses) via CRLF injection in:

  • Non-link header values
  • Non-link header names
  • Link header URLs (the linkValueRegExp regex uses [^>]* which matches \r\n)

This PR adds the same validation used by setHeader():

Call validateHeaderName() and validateHeaderValue() for non-link headers in the writeEarlyHints loop
Call checkInvalidHeaderChar() on the assembled Link header value
Tighten linkValueRegExp from [^>]* to [^>\r\n]* to reject CRLF inside the <...> URL portion

The HTTP/2 compatibility layer (lib/internal/http2/compat.js) has the same gap but nghttp2 rejects CRLF in HTTP/2 binary frame headers at the lower level. Though, it could be open for discussion whether similar validation should also be applied?

Add validateHeaderName/validateHeaderValue checks for non-link
headers and checkInvalidHeaderChar for the Link value in HTTP/1.1
writeEarlyHints, closing a CRLF injection gap where header names
and values were concatenated into the raw response without
validation.

Also tighten linkValueRegExp to reject CR/LF inside the <...>
URL portion of Link header values.
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Feb 20, 2026
@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 72.72727% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.76%. Comparing base (f632f3f) to head (dc05fe1).
⚠️ Report is 66 commits behind head on main.

Files with missing lines Patch % Lines
lib/_http_server.js 70.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #61897      +/-   ##
==========================================
- Coverage   91.70%   89.76%   -1.94%     
==========================================
  Files         337      674     +337     
  Lines      140104   205614   +65510     
  Branches    21758    39410   +17652     
==========================================
+ Hits       128484   184578   +56094     
- Misses      11398    13277    +1879     
- Partials      222     7759    +7537     
Files with missing lines Coverage Δ
lib/internal/validators.js 98.25% <100.00%> (+12.06%) ⬆️
lib/_http_server.js 96.90% <70.00%> (-0.20%) ⬇️

... and 459 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l'atm

@mcollina
Copy link
Member

mcollina commented Feb 22, 2026

Can you check linting?

Copy link
Member

@pimterry pimterry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍. Thanks @rsclarke!

@pimterry pimterry added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 25, 2026
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 25, 2026
@nodejs-github-bot
Copy link
Collaborator

@pimterry pimterry added commit-queue Add this label to land a pull request using GitHub Actions. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. labels Mar 2, 2026
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Mar 2, 2026
@nodejs-github-bot nodejs-github-bot merged commit e78bf55 into nodejs:main Mar 2, 2026
70 checks passed
@nodejs-github-bot
Copy link
Collaborator

Landed in e78bf55

richardlau pushed a commit that referenced this pull request Mar 2, 2026
Add validateHeaderName/validateHeaderValue checks for non-link
headers and checkInvalidHeaderChar for the Link value in HTTP/1.1
writeEarlyHints, closing a CRLF injection gap where header names
and values were concatenated into the raw response without
validation.

Also tighten linkValueRegExp to reject CR/LF inside the <...>
URL portion of Link header values.

PR-URL: #61897
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Mar 11, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [node](https://nodejs.org) ([source](https://github.com/nodejs/node)) | minor | `25.7.0` → `25.8.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>nodejs/node (node)</summary>

### [`v25.8.0`](https://github.com/nodejs/node/releases/tag/v25.8.0): 2026-03-03, Version 25.8.0 (Current), @&#8203;richardlau

[Compare Source](nodejs/node@v25.7.0...v25.8.0)

##### Notable Changes

- \[[`e55eddea2a`](nodejs/node@e55eddea2a)] - **build, doc**: use new api doc tooling (flakey5) [#&#8203;57343](nodejs/node#57343)
- \[[`4c181e2277`](nodejs/node@4c181e2277)] - **(SEMVER-MINOR)** **sqlite**: add limits property to DatabaseSync (Mert Can Altin) [#&#8203;61298](nodejs/node#61298)
- \[[`46ee1eddd7`](nodejs/node@46ee1eddd7)] - **(SEMVER-MINOR)** **src**: add C++ support for diagnostics channels (RafaelGSS) [#&#8203;61869](nodejs/node#61869)
- \[[`9ddd1a9c27`](nodejs/node@9ddd1a9c27)] - **(SEMVER-MINOR)** **src,permission**: add --permission-audit (RafaelGSS) [#&#8203;61869](nodejs/node#61869)
- \[[`0d97ec4044`](nodejs/node@0d97ec4044)] - **(SEMVER-MINOR)** **test\_runner**: expose worker ID for concurrent test execution (Ali Hassan) [#&#8203;61394](nodejs/node#61394)

##### Commits

- \[[`940b58c8c1`](nodejs/node@940b58c8c1)] - **buffer**: optimize buffer.concat performance (Mert Can Altin) [#&#8203;61721](nodejs/node#61721)
- \[[`0589b0e5a1`](nodejs/node@0589b0e5a1)] - **build**: fix GN for new merve dep (Shelley Vohr) [#&#8203;61984](nodejs/node#61984)
- \[[`f3d3968dcd`](nodejs/node@f3d3968dcd)] - ***Revert*** "**build**: add temporal test on GHA windows" (Antoine du Hamel) [#&#8203;61810](nodejs/node#61810)
- \[[`e55eddea2a`](nodejs/node@e55eddea2a)] - **build, doc**: use new api doc tooling (flakey5) [#&#8203;57343](nodejs/node#57343)
- \[[`b7715292f8`](nodejs/node@b7715292f8)] - **child\_process**: add tracing channel for spawn (Marco) [#&#8203;61836](nodejs/node#61836)
- \[[`a32a598748`](nodejs/node@a32a598748)] - **crypto**: fix missing nullptr check on RSA\_new() (ndossche) [#&#8203;61888](nodejs/node#61888)
- \[[`dc384f95b3`](nodejs/node@dc384f95b3)] - **crypto**: fix handling of null BUF\_MEM\* in ToV8Value() (Nora Dossche) [#&#8203;61885](nodejs/node#61885)
- \[[`3337b095db`](nodejs/node@3337b095db)] - **crypto**: fix potential null pointer dereference when BIO\_meth\_new() fails (Nora Dossche) [#&#8203;61788](nodejs/node#61788)
- \[[`51ded81139`](nodejs/node@51ded81139)] - **deps**: update undici to 7.22.0 (Node.js GitHub Bot) [#&#8203;62035](nodejs/node#62035)
- \[[`8aa2fde931`](nodejs/node@8aa2fde931)] - **deps**: update minimatch to 10.2.4 (Node.js GitHub Bot) [#&#8203;62016](nodejs/node#62016)
- \[[`57dc092eaf`](nodejs/node@57dc092eaf)] - **deps**: upgrade npm to 11.11.0 (npm team) [#&#8203;61994](nodejs/node#61994)
- \[[`705bbd60a9`](nodejs/node@705bbd60a9)] - **deps**: update simdjson to 4.3.1 (Node.js GitHub Bot) [#&#8203;61930](nodejs/node#61930)
- \[[`4d411d72e5`](nodejs/node@4d411d72e5)] - **deps**: update acorn-walk to 8.3.5 (Node.js GitHub Bot) [#&#8203;61928](nodejs/node#61928)
- \[[`f53a32ab84`](nodejs/node@f53a32ab84)] - **deps**: update acorn to 8.16.0 (Node.js GitHub Bot) [#&#8203;61925](nodejs/node#61925)
- \[[`9b483fbb27`](nodejs/node@9b483fbb27)] - **deps**: update minimatch to 10.2.2 (Node.js GitHub Bot) [#&#8203;61830](nodejs/node#61830)
- \[[`4e54c103cb`](nodejs/node@4e54c103cb)] - **doc**: separate in-types and out-types in SQLite conversion docs (René) [#&#8203;62034](nodejs/node#62034)
- \[[`ca78ebbeaa`](nodejs/node@ca78ebbeaa)] - **doc**: fix small logic error in DETECT\_MODULE\_SYNTAX (René) [#&#8203;62025](nodejs/node#62025)
- \[[`e6b131f3fe`](nodejs/node@e6b131f3fe)] - **doc**: fix module.stripTypeScriptTypes indentation (René) [#&#8203;61992](nodejs/node#61992)
- \[[`7508540e19`](nodejs/node@7508540e19)] - **doc**: update DEP0040 (punycode) to application type deprecation (Mike McCready) [#&#8203;61916](nodejs/node#61916)
- \[[`33a364cb62`](nodejs/node@33a364cb62)] - **doc**: explicitly mention Slack handle (Rafael Gonzaga) [#&#8203;61986](nodejs/node#61986)
- \[[`46a61922bd`](nodejs/node@46a61922bd)] - **doc**: support toolchain Visual Studio 2022 & 2026 + Windows 11 SDK (Mike McCready) [#&#8203;61864](nodejs/node#61864)
- \[[`dc12a257aa`](nodejs/node@dc12a257aa)] - **doc**: rename invalid `function` parameter (René) [#&#8203;61942](nodejs/node#61942)
- \[[`dafdc0a5b8`](nodejs/node@dafdc0a5b8)] - **http**: validate headers in writeEarlyHints (Richard Clarke) [#&#8203;61897](nodejs/node#61897)
- \[[`3c94b56fa6`](nodejs/node@3c94b56fa6)] - **inspector**: unwrap internal/debugger/inspect imports (René) [#&#8203;61974](nodejs/node#61974)
- \[[`8a24c17648`](nodejs/node@8a24c17648)] - **lib**: improve argument handling in Blob constructor (Ms2ger) [#&#8203;61980](nodejs/node#61980)
- \[[`21d4baf256`](nodejs/node@21d4baf256)] - **meta**: bump github/codeql-action from 4.32.0 to 4.32.4 (dependabot\[bot]) [#&#8203;61911](nodejs/node#61911)
- \[[`59a726a8e3`](nodejs/node@59a726a8e3)] - **meta**: bump step-security/harden-runner from 2.14.1 to 2.14.2 (dependabot\[bot]) [#&#8203;61909](nodejs/node#61909)
- \[[`0072b7f991`](nodejs/node@0072b7f991)] - **meta**: bump actions/stale from 10.1.1 to 10.2.0 (dependabot\[bot]) [#&#8203;61908](nodejs/node#61908)
- \[[`999bf22f47`](nodejs/node@999bf22f47)] - **repl**: keep reference count for `process.on('newListener')` (Anna Henningsen) [#&#8203;61895](nodejs/node#61895)
- \[[`4c181e2277`](nodejs/node@4c181e2277)] - **(SEMVER-MINOR)** **sqlite**: add limits property to DatabaseSync (Mert Can Altin) [#&#8203;61298](nodejs/node#61298)
- \[[`aee2a18257`](nodejs/node@aee2a18257)] - **src**: fix flags argument offset in JSUdpWrap (Weixie Cui) [#&#8203;61948](nodejs/node#61948)
- \[[`46ee1eddd7`](nodejs/node@46ee1eddd7)] - **(SEMVER-MINOR)** **src**: add C++ support for diagnostics channels (RafaelGSS) [#&#8203;61869](nodejs/node#61869)
- \[[`9ddd1a9c27`](nodejs/node@9ddd1a9c27)] - **(SEMVER-MINOR)** **src,permission**: add --permission-audit (RafaelGSS) [#&#8203;61869](nodejs/node#61869)
- \[[`ea2df2a16f`](nodejs/node@ea2df2a16f)] - **stream**: fix pipeTo to defer writes per WHATWG spec (Matteo Collina) [#&#8203;61800](nodejs/node#61800)
- \[[`aa0c7b09e0`](nodejs/node@aa0c7b09e0)] - **test**: remove unnecessary `process.exit` calls from test files (Antoine du Hamel) [#&#8203;62020](nodejs/node#62020)
- \[[`ad96a6578f`](nodejs/node@ad96a6578f)] - **test**: skip `test-url` on `--shared-ada` builds (Antoine du Hamel) [#&#8203;62019](nodejs/node#62019)
- \[[`7c72a31e4b`](nodejs/node@7c72a31e4b)] - **test**: skip strace test with shared openssl (Richard Lau) [#&#8203;61987](nodejs/node#61987)
- \[[`604456c163`](nodejs/node@604456c163)] - **test**: avoid flaky debugger restart waits (Yuya Inoue) [#&#8203;61773](nodejs/node#61773)
- \[[`4890d6bd43`](nodejs/node@4890d6bd43)] - **test\_runner**: run afterEach on runtime skip (Igor Shevelenkov) [#&#8203;61525](nodejs/node#61525)
- \[[`fce2930110`](nodejs/node@fce2930110)] - **test\_runner**: expose expectFailure message (sangwook) [#&#8203;61563](nodejs/node#61563)
- \[[`0d97ec4044`](nodejs/node@0d97ec4044)] - **(SEMVER-MINOR)** **test\_runner**: expose worker ID for concurrent test execution (Ali Hassan) [#&#8203;61394](nodejs/node#61394)
- \[[`243e6b2009`](nodejs/node@243e6b2009)] - **test\_runner**: replace native methods with primordials (Ayoub Mabrouk) [#&#8203;61219](nodejs/node#61219)
- \[[`bf1ed7e647`](nodejs/node@bf1ed7e647)] - **tls**: forward keepAlive, keepAliveInitialDelay, noDelay to socket (Sergey Zelenov) [#&#8203;62004](nodejs/node#62004)
- \[[`0f15079d94`](nodejs/node@0f15079d94)] - **tools**: remove custom logic for skipping `test-strace-openat-openssl` (Antoine du Hamel) [#&#8203;62038](nodejs/node#62038)
- \[[`54a055a59d`](nodejs/node@54a055a59d)] - **tools**: bump minimatch from 3.1.2 to 3.1.3 in `/tools/clang-format` (dependabot\[bot]) [#&#8203;61977](nodejs/node#61977)
- \[[`a28744cb62`](nodejs/node@a28744cb62)] - **tools**: fix permissions for merve update script (Richard Lau) [#&#8203;62023](nodejs/node#62023)
- \[[`31e7936354`](nodejs/node@31e7936354)] - **tools**: revert tools GHA workflow to ubuntu-latest (Richard Lau) [#&#8203;62024](nodejs/node#62024)
- \[[`0a96a16e1f`](nodejs/node@0a96a16e1f)] - **tools**: bump minimatch from 3.1.2 to 3.1.3 in /tools/eslint (dependabot\[bot]) [#&#8203;61976](nodejs/node#61976)
- \[[`f279233412`](nodejs/node@f279233412)] - **tools**: roll back to x86 runner on `scorecard.yml` (Antoine du Hamel) [#&#8203;61944](nodejs/node#61944)
- \[[`192c0382f4`](nodejs/node@192c0382f4)] - **util**: add fast path to stripVTControlCharacters (Hiroki Osame) [#&#8203;61833](nodejs/node#61833)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40OS4wIiwidXBkYXRlZEluVmVyIjoiNDMuNDkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants