Skip to content

docs: security caveats for cors, proxy, redirect, host and static utils#1470

Merged
pi0 merged 4 commits into
mainfrom
docs/security-caveats
Jul 15, 2026
Merged

docs: security caveats for cors, proxy, redirect, host and static utils#1470
pi0 merged 4 commits into
mainfrom
docs/security-caveats

Conversation

@pi0x

@pi0x pi0x commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Documentation/JSDoc-only changes (no behavioral code changes) covering five security caveats:

  • CORS regex origins (src/utils/cors.ts, test/unit/cors.test.ts): document that regex origin entries match unanchored and must be anchored + escaped; replace misleading insecure test/doc examples ([/example/], [/example.com/]) with anchored patterns.
  • Proxy credential forwarding (src/utils/proxy.ts): proxy/proxyRequest forward Cookie/Authorization verbatim to the target — correct for a same-trust reverse proxy, a leak to untrusted upstreams. Recommend filterHeaders: ["cookie", "authorization"], noting the contrast with fetchWithEvent.
  • redirect() open-redirect (src/utils/response.ts): security note (mirroring redirectBack) to validate user-derived location against an allow-list.
  • Host-header trust (src/utils/request.ts): getRequestURL().origin/.host and getRequestHost() reflect the client-supplied Host header and must not be trusted for security decisions unless pinned/validated upstream.
  • Static-serving caveats (src/utils/static.ts): serveStatic keeps %2f/%5c encoded and safety depends on getContents/getMeta not decoding them; also document case-insensitive filesystem case-folding and symlink containment (realpath) responsibilities.

Regenerated the affected automd docs pages (docs/2.utils/{1.request,2.response,5.proxy}.md). pnpm vitest run test/unit/cors.test.ts and pnpm lint pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added security guidance for request host and URL values derived from client-controlled headers.
    • Documented open redirect risks and validation requirements for redirects.
    • Added path traversal and filesystem safety guidance for static asset serving (percent-encoded separator handling, backend decoding responsibilities, symlink containment).
    • Clarified credential forwarding behavior in proxy utilities and how to prevent unintended exposure.
    • Expanded CORS documentation with safe anchoring and escaping practices for regex-based origins.
  • Tests
    • Tightened CORS unit tests to require securely anchored/escaped origin patterns and added regression coverage for rejected attacker-controlled origins.

…tatic utils

- CORS: warn that regex `origin` entries match unanchored and must be
  anchored/escaped; fix misleading test examples to safe patterns.
- proxy/proxyRequest: document that `Cookie`/`Authorization` are forwarded
  verbatim and recommend `filterHeaders` for untrusted upstreams.
- redirect(): add open-redirect note mirroring `redirectBack`.
- getRequestURL/getRequestHost: warn the client-supplied `Host` must not be
  trusted for security decisions unless pinned/validated upstream.
- serveStatic/getMeta/getContents: document that `%2f`/`%5c` stay encoded and
  safety depends on backends not decoding them; note case-fold and symlink
  containment responsibilities.

Co-Authored-By: Claude Fable 5 <[email protected]>
@pi0x
pi0x requested a review from pi0 as a code owner July 14, 2026 12:38
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5644e2ca-35d4-4c81-b5e0-9d3fb6075209

📥 Commits

Reviewing files that changed from the base of the PR and between 20401f6 and 4fb7348.

📒 Files selected for processing (3)
  • src/utils/cors.ts
  • src/utils/proxy.ts
  • test/unit/cors.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/unit/cors.test.ts
  • src/utils/proxy.ts

📝 Walkthrough

Walkthrough

Changes

The PR adds security guidance to request, proxy, response, static-file, and CORS utility documentation. CORS tests update regex examples and boundary assertions to require anchored, escaped origin patterns.

Security documentation and validation

Layer / File(s) Summary
Request host trust guidance
docs/2.utils/1.request.md, src/utils/request.ts
Documents spoofable host and origin values derived from request headers and distinguishes them from URL path components.
Proxy credential forwarding guidance
docs/2.utils/5.proxy.md, src/utils/proxy.ts
Documents credential-header forwarding behavior and recommends filtering Cookie and Authorization for untrusted targets.
Redirect and static asset safety guidance
docs/2.utils/2.response.md, src/utils/response.ts, src/utils/static.ts
Adds open-redirect prevention and encoded-path, filesystem-case, and symlink-containment guidance.
CORS regular-expression guidance
src/utils/cors.ts, test/unit/cors.test.ts
Explains unanchored regex matching and validates exact origin boundaries with anchored, escaped patterns.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • h3js/h3#1316: Related to host-header handling in getRequestHost and getRequestURL.
  • h3js/h3#1428: Related to encoded path traversal behavior in serveStatic.
  • h3js/h3#1455: Related to proxy header-forwarding and filtering behavior.

Suggested reviewers: pi0, francisjohnjohnston-web

Poem

A rabbit reads warnings by moonlight bright,
Anchored regexes guard the night.
Cookies stay tucked when upstreams stray,
Safe paths hop along their way.
Redirects now pause before they dance.
Docs keep security in balance.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the docs-only security caveats added across the affected utilities.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/security-caveats

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/2.utils/2.response.md`:
- Around line 67-70: Correct the encoded-backslash security documentation
consistently at docs/2.utils/2.response.md lines 67-70, src/utils/static.ts
lines 20-34, and src/utils/static.ts lines 73-88: explain that single-encoded
%5c is decoded and normalized before getMeta/getContents lookup, while
double-encoded %255c may survive as literal %5c. Retain the warning that
callback backends must not decode the ID, and preserve the case-folding and
symlink-containment guidance where applicable.

In `@src/utils/cors.ts`:
- Around line 26-29: Update the documentation example near the regex-origin
guidance to match its stated behavior: either change “subdomains” to “an
optional subdomain” for the existing single-label pattern, or revise the pattern
to repeat the escaped subdomain label when arbitrary nested subdomains are
intended.

In `@src/utils/proxy.ts`:
- Around line 256-263: Update proxy() in src/utils/proxy.ts#L256-L263 to
implement the documented filterHeaders support, ensuring configured headers such
as Cookie and Authorization are removed before forwarding opts.headers to the
target. Regenerate docs/2.utils/5.proxy.md#L39-L40 from the corrected source
JSDoc so the documentation accurately describes the working credential-filtering
contract.

In `@src/utils/request.ts`:
- Around line 472-478: The JSDoc currently overstates the safety of URL
components. In src/utils/request.ts lines 472-478, revise the wording to state
that pathname and search are not derived from the spoofable host while still
treating them as untrusted input requiring validation or encoding for their
destination sink; update docs/2.utils/1.request.md lines 256-257, or regenerate
it, to match the qualified wording.

In `@test/unit/cors.test.ts`:
- Around line 238-241: Add negative test cases for https://example.com.evil.test
and https://notexample.com in both isCorsOriginAllowed and createOriginHeaders
coverage, asserting each origin is rejected and does not produce allowed CORS
headers. Keep the existing anchored regex configuration and positive cases
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc058801-561a-4b51-9497-c8157b4d1963

📥 Commits

Reviewing files that changed from the base of the PR and between 2264316 and 57511a0.

📒 Files selected for processing (9)
  • docs/2.utils/1.request.md
  • docs/2.utils/2.response.md
  • docs/2.utils/5.proxy.md
  • src/utils/cors.ts
  • src/utils/proxy.ts
  • src/utils/request.ts
  • src/utils/response.ts
  • src/utils/static.ts
  • test/unit/cors.test.ts

Comment thread docs/2.utils/2.response.md Outdated
Comment thread src/utils/cors.ts Outdated
Comment thread src/utils/proxy.ts Outdated
Comment thread src/utils/request.ts
Comment thread test/unit/cors.test.ts

@pi0x pi0x left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

REQUEST CHANGES — one technical inaccuracy in the proxy() credential note; everything else is accurate and well done.

Independent review (I did not author this). Verified against the source on the branch:

Accurate claims (verified in code):

  • CORS unanchored: isCorsOriginAllowed uses _origin.test(origin) (unanchored) — note is correct. New anchored test regexes (/^https:\/\/([a-z0-9-]+\.)?example\.com$/, /^https?:\/\/example\.com$/) are valid inputs and the suite passes.
  • static %2f/%5c: serveStatic runs resolveDotSegments then decodeURI, which preserves reserved %2f; traversal safety does depend on getMeta/getContents not decoding — accurate.
  • redirect/redirectBack: redirectBack enforces refererURL.origin === event.url.origin — the "same-origin referers" wording is correct.
  • Host-header trust and getRequestURL/getRequestHost notes are accurate.
  • fetchWithEvent "never forwards the event headers to an external URL": correct — the external branch calls fetch(url, init) with no event headers.

No behavioral code changes anywhere in the diff. pnpm vitest run test/unit/cors.test.ts test/proxy.test.ts test/static.test.ts and pnpm lint pass. (The npx automd drift in docs/2.utils/{4.security,5.proxy,9.more}.md is pre-existing on main — emphasis-style/code-wrap only — not introduced here.)

Requested change — proxy() credential note is inaccurate:
The proxyRequest note is correct: it calls getProxyRequestHeaders(event, { filterHeaders }), so filterHeaders: ["cookie","authorization"] does strip them.

But the note added to the lower-level proxy() (src/utils/proxy.ts ~L257-263, mirrored in docs/2.utils/5.proxy.md) recommends the same filterHeaders: ["cookie","authorization"] — and proxy() never reads opts.filterHeaders (grep confirms it is only consumed in getProxyRequestHeaders, i.e. by proxyRequest/fetchWithEvent). proxy() forwards only headers the caller explicitly passes via opts.headers/opts.fetchOptions.headers; it does not auto-forward the incoming request's Cookie/Authorization at all. So on proxy() the recommended mitigation is a silent no-op — worse than no advice in a security note.

Suggested fix: on the proxy() docstring, either point readers to proxyRequest for filterHeaders-based stripping, or state that proxy() forwards only caller-supplied opts.headers verbatim and credentials are controlled by not passing them (filterHeaders has no effect here). Then regenerate docs/2.utils/5.proxy.md.

🤖 Generated with Claude Code

`proxy()` never reads `opts.filterHeaders` (only `proxyRequest` and
`getProxyRequestHeaders` do) and does not auto-forward the incoming
request's headers — it only sends what the caller passes via
`opts.headers`/`fetchOptions.headers`. Rewrite the note accordingly and
point to `proxyRequest` for the `filterHeaders` mitigation.

Co-Authored-By: Claude Fable 5 <[email protected]>
@pi0x

pi0x commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in 142ea20: the credential-forwarding note on the low-level proxy() was inaccurate — proxy() never consumes opts.filterHeaders and does not auto-forward the incoming request's headers (it only sends what the caller passes via opts.headers/fetchOptions.headers). The note now states that directly and points to proxyRequest for the filterHeaders: ["cookie", "authorization"] mitigation. The proxyRequest note is unchanged. Docs page regenerated via automd; pnpm lint and pnpm vitest run test/proxy.test.ts pass.

pi0x

This comment was marked as duplicate.

pi0 and others added 2 commits July 15, 2026 08:20
Address CodeRabbit review on #1470:

- static: correct the encoded-separator note — `%2f` always survives, but a
  single-encoded `%5c` is decoded to `\` and normalized away; only a
  double-encoded `%255c` reaches getMeta/getContents as literal `%5c`.
- cors: fix regex example wording — `([a-z0-9-]+\.)?` permits one optional
  subdomain label, not arbitrary depth.
- request: qualify `.pathname`/`.search` as not host-derived but still
  untrusted input requiring validation/encoding at their sink.
- cors test: add negative regex-boundary assertions (`example.com.evil.test`,
  `notexample.com`) for isCorsOriginAllowed and createOriginHeaders.

Regenerated affected automd pages.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pi0
pi0 merged commit c97ed61 into main Jul 15, 2026
7 of 9 checks passed
@pi0
pi0 deleted the docs/security-caveats branch July 15, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants