Skip to content

🐛 fix: match HTTP field names the way a recipient must - #4585

Merged
ReneWerner87 merged 57 commits into
claude/split-1-url-compositionfrom
claude/split-2-header-matching
Aug 11, 2026
Merged

ReneWerner87 merged 57 commits into
claude/split-1-url-compositionfrom
claude/split-2-header-matching

Conversation

@gaby

@gaby gaby commented Aug 7, 2026

Copy link
Copy Markdown
Member

Description

2 of 3, split out of #4581. Based on #4584 — review that one first, or read this diff against claude/split-1-url-composition.

Field names are case-insensitive (RFC 9110 §5.1), but fasthttp's Peek, PeekAll and Del compare the stored key byte for byte. That is enough while fasthttp canonicalizes it — under DisableHeaderNormalizing the store keeps the spelling the client sent, and lower case is what HTTP/2 and HTTP/3 put on the wire, so it is what a front end translating down to HTTP/1.1 preserves. Every guard below was then reading a header that was there as if it were absent.

Measured, with DisableHeaderNormalizing set:

Peek("Authorization") = ""            stored key: "authorization"
Peek("Content-Type")  = "text/plain"  <- special slot, works either way

That last line is why this survived review: the names fasthttp keeps in a slot of their own still resolve, so a spot check on the obvious header passes.

What was wrong

cache

An anonymous request was served another user's authenticated page. A lower-case authorization: read as no credential at all, so the response was stored as anonymous and replayed to everyone — bearer token included. Set-Cookie2, Vary and KeyHeaders missed the same way, storing and replaying a personalized response and dropping a Vary dimension silently out of the key. public was injected into a response that never said it — exactly the token that lifts RFC 9111 §3.5 for a response to an authenticated request.

Unrelated to case, in the same paths: a cached 3xx lost its Location, so the first client got 301 Location: /new and every one after a bare 301; a Set-Cookie on a shared entry handed one client's session to everyone matching the key; and a repeated header name was collapsed on replay, so a response sending Vary twice came back varying only on the second.

proxy

Every sanitizer removed nothing. A cross-host redirect carried the caller's authorization: and cookie: to the redirect target; upgrade, te, keep-alive and a connection:-listed header reached the upstream; and the client's own x-real-ip arrived beside the one the proxy writes, so the upstream attributed the request to an address the client chose.

The response strip also asked the wrong object: a proxied response is parsed by the outbound fasthttp.Client, which carries its own setting and stamps it onto that header each hop. A proxy preserving upstream casing in front of a normalizing app stripped nothing.

csrf

The origin check was switched off by the case of a header name. With a valid double-submit pair, Origin: http://evil.com → 403, but origin: http://evil.com200. An absent Origin is not a failure there — on a plaintext connection the check is skipped for it entirely.

others

  • extractors.FromHeader: a token sent under the lower-case name was not found, and the request refused for carrying no token when it carried one.
  • logger: ${ips} logged an empty chain while the trust decisions went on using the header it could not see; RegisterContextTag wrote its value raw, where a CR or LF forged a log line.
  • adaptor: a wrapped net/http middleware's header edits were lost — a Header.Set collapsing a multi-valued header left the other values in place, a middleware that changed nothing duplicated the last value of every multi-valued header, and removals never propagated at all.
  • form parsing: fasthttp matches the media type and boundary= case-sensitively, so a legal Multipart/Form-Data bound nothing and reported no error.
  • Scheme(): only http and https are accepted from a proxy header. The value is spliced into a URL by BaseURL and compared for origin equality by csrf and Redirect().Back, so no other scheme — javascript included — may become the request's.

basicauth and cors were checked too and fail closed — a lower-case name locks a legitimate client out, never lets one in — so they are left alone and the finding is recorded rather than acted on.

Changes introduced

  • Benchmarks: two measurements changed the design rather than confirming it. The first delField cost 792 B/op and ran 3× slower on the proxy's default path (a range-over-func iterator allocation per name), so the normalization answer is threaded from config instead — measured back to 0 B/op and the original ns/op. An earlier header dedup in adaptor was 17% slower at 20 headers and 61% at 100, so it was removed.
  • Documentation Update: docs/middleware/cache.md, docs/middleware/proxy.md (an X-Real-IP caution and a two-pass snippet), docs/middleware/logger.md (${ips}), docs/api/ctx.md, docs/extra/internal.md.
  • Changelog/What's New: not written yet.
  • Migration Guide: not needed — see below.
  • API Alignment with Express: no API surface changes.
  • API Longevity: no signature changes to exported APIs. Four internal/ packages hold the shared primitives so the duplication that caused one of these cannot cause the next: fieldname (case-insensitive read and delete over fasthttp header types), headerlookup (the same for a caller holding a Ctx), mediatype (the Content-Type fold every form entry point needs) and crosshost (the credential list the client and proxy had already let drift apart).
  • Examples: covered by the tests rather than separately.

Behavior changes worth knowing about

  • A response that sets a cookie is no longer stored by the cache unless it opts in with public or s-maxage. Applications that refresh a session cookie on every response will find most routes stop being cached — that is the point, since those responses are per-client. must-revalidate does not lift this: RFC 9111 §3.5 accepts it for Authorization because a revalidating cache re-checks the credential at the origin, and this middleware never revalidates.
  • A proxy header naming a scheme other than http/https no longer becomes the request's scheme; the previously determined one stands.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Documentation update (changes to documentation)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community. (no new module dependencies; four new internal/ packages)
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Verification

go build ./..., go vet ./..., go test ./..., gofmt -l . and golangci-lint v2.12.2 (the version .github/workflows/lint.yml pins) — 0 issues — all run on this branch.

Every fix is mutation-verified: reproduced with a probe first, then the fix reverted in a scratch copy, the new test confirmed failing, then restored.


Generated by Claude Code

@gaby
gaby requested a review from a team as a code owner August 7, 2026 05:39
@gaby
gaby requested review from ReneWerner87, efectn and sixcolors and removed request for a team August 7, 2026 05:39
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • master
  • v2
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8eec917b-910b-4007-9963-841830130303

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The PR adds shared case-insensitive header and media-type utilities. It updates form parsing, caching, proxy filtering, middleware synchronization, CSRF checks, logging, scheme validation, and related documentation and tests.

Changes

Header normalization and request processing

Layer / File(s) Summary
Media-type normalization and form binding
internal/mediatype/*, binder/*, bind.go, helpers.go, req.go, redirect.go
Form media types and parameter names are normalized while multipart boundary values remain unchanged.
Header lookup and middleware synchronization
internal/fieldname/*, internal/headerlookup/*, middleware/adaptor/*, extractors/*
Header access, deletion, extraction, and middleware synchronization now support non-canonical field names and repeated values.
Cache key and response handling
middleware/cache/*
Cache keys include all header field lines. Cached responses replace stale header spellings, exclude cookie headers, preserve repeated fields, and retain redirect locations.
Proxy filtering and redirects
middleware/proxy/*, internal/crosshost/*
Proxy forwarding replaces all client-supplied X-Real-IP fields, strips hop-by-hop headers case-insensitively, and removes sensitive headers across cross-host redirects.
Security and logging consumers
middleware/csrf/*, middleware/logger/*
CSRF header reads use case-insensitive lookup. IP logging uses the parsed proxy chain and sanitizes output.
Documentation and regression coverage
docs/*, ctx_test.go, related tests
Documentation records content-type mutation, scheme restrictions, cache behavior, proxy header handling, logger behavior, and flash-cookie properties.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: v3

Suggested reviewers: efectn, sixcolors, renewerner87

Poem

I nibbled the headers, both lowercase and bright,
Bound forms through boundaries, preserved values just right.
Cache keys grew careful, proxies shed stale disguise,
Logs kept their newlines safely out of sight.
Hop, hop—the rabbit approves this rewrite!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix for case-insensitive HTTP field-name handling.
Description check ✅ Passed The description is detailed and covers the problem, changes, behavior impacts, tests, documentation, benchmarks, and verification.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/split-2-header-matching

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.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.06742% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.69%. Comparing base (e98ccd8) to head (ef43f3f).

Files with missing lines Patch % Lines
client/transport.go 91.93% 3 Missing and 2 partials ⚠️
middleware/cache/cache.go 93.93% 2 Missing and 2 partials ⚠️
client/cookiejar.go 75.00% 1 Missing and 2 partials ⚠️
middleware/cache/keygen.go 86.36% 2 Missing and 1 partial ⚠️
middleware/cache/utils.go 95.94% 2 Missing and 1 partial ⚠️
req.go 89.47% 2 Missing ⚠️
middleware/logger/context_tag.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@                        Coverage Diff                         @@
##           claude/split-1-url-composition    #4585      +/-   ##
==================================================================
+ Coverage                           93.58%   93.69%   +0.11%     
==================================================================
  Files                                 140      140              
  Lines                               15605    15978     +373     
==================================================================
+ Hits                                14604    14971     +367     
+ Misses                                627      624       -3     
- Partials                              374      383       +9     
Flag Coverage Δ
unittests 93.69% <96.06%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

gaby commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

Auto-review is off for this one because the base is claude/split-1-url-composition (#4584) rather than main — this is 2 of 3 in a chained split of #4581, so it has to stay stacked until #4584 merges. Requesting a review explicitly so this doesn't go unreviewed in the meantime; it carries the largest share of the security fixes.

Note the branch was force-pushed once, rebasing onto #4584's new tip after a documentation fix there. No code changed in the rebase.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@gaby I will review pull request #4585 against its stacked base, #4584.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 417636b676

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread middleware/cache/cache.go
Comment thread internal/mediatype/mediatype.go
Comment thread docs/middleware/logger.md Outdated
Comment thread middleware/cache/cache.go Outdated
Comment thread middleware/cache/cache.go
Field names are case-insensitive (RFC 9110 §5.1), but fasthttp's Peek,
PeekAll and Del compare the stored key byte for byte. That is enough while
fasthttp canonicalizes it — under DisableHeaderNormalizing the store keeps
the spelling the client sent, and lower case is what HTTP/2 and HTTP/3 put
on the wire, so it is what a front end translating down to HTTP/1.1
preserves. Every guard below was then reading a header that was there as
if it were absent.

cache: an anonymous request was served another user's authenticated page.
A lower-case "authorization:" read as no credential at all, so the
response was stored as anonymous and replayed to everyone — bearer token
included. Set-Cookie2, Vary and KeyHeaders missed the same way, storing
and replaying a personalized response and dropping a Vary dimension
silently out of the key. "public" was injected into a response that never
said it, which is exactly the token that lifts RFC 9111 §3.5 for a
response to an authenticated request.

Also in cache, unrelated to case: a cached 3xx lost its Location, so the
first client got "301 Location: /new" and every one after it a bare 301; a
Set-Cookie on a shared entry handed one client's session to everyone
matching the key; and a repeated header name was collapsed on replay, so a
response sending Vary twice came back varying only on the second.

proxy: every sanitizer removed nothing. A cross-host redirect carried the
caller's "authorization:" and "cookie:" to the redirect target; "upgrade",
"te", "keep-alive" and a "connection:"-listed header reached the upstream;
and the client's own "x-real-ip" arrived beside the one the proxy writes,
so the upstream attributed the request to an address the client chose. The
response strip also asked the wrong object — a proxied response is parsed
by the outbound fasthttp.Client, which carries its own setting.

csrf: the origin check was switched off by the case of a header name. With
a valid double-submit pair, "Origin: http://evil.com" gave 403 but
"origin: http://evil.com" gave 200, because an absent Origin is not a
failure on a plaintext connection.

extractors.FromHeader: a token sent under the lower-case name was not
found, and the request refused for carrying no token when it carried one.

logger: ${ips} logged an empty chain while the trust decisions went on
using the header it could not see, and RegisterContextTag wrote its value
raw, where a CR or LF forged a log line.

adaptor: a wrapped net/http middleware's header edits were lost. A
Header.Set collapsing a multi-valued header left the other values in
place, a middleware that changed nothing duplicated the last value of
every multi-valued header, and removals never propagated at all.

form parsing: fasthttp matches the media type and "boundary=" case
sensitively, so a legal "Multipart/Form-Data" bound nothing and reported
no error. Every entry point that reaches those parsers now folds first.

Scheme(): only "http" and "https" are accepted from a proxy header. The
value is spliced into a URL by BaseURL and compared for origin equality by
csrf and Redirect().Back, so no other scheme may become the request's.

basicauth and cors were checked too and fail closed — a lower-case name
locks a legitimate client out, never lets one in — so they are left alone.

Four internal packages hold the shared primitives, so the duplication that
caused one of these cannot cause the next: fieldname (case-insensitive
read and delete), headerlookup (the same for a caller holding a Ctx),
mediatype (the Content-Type fold) and crosshost (the credential list the
client and proxy had already let drift apart).

Benchmarked: the first delField cost 792 B/op and ran 3x slower on the
proxy's default path, so the normalization answer is threaded from config
instead — measured back to 0 B/op and the original ns/op. An earlier
header dedup in adaptor was 17% slower at twenty headers and 61% at a
hundred, so it was removed.

Split out of #4581, on top of the open-redirect fixes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
@gaby
gaby force-pushed the claude/split-2-header-matching branch from ddc5560 to 5d8b101 Compare August 7, 2026 05:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
middleware/adaptor/adaptor_test.go (1)

2300-2377: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a request with header normalization disabled.

Every subtest uses fiber.New(), which keeps fasthttp header normalization enabled, and every header name is already canonical. The new isFramingHeader and clearCopiedHeaders logic exists to survive non-canonical storage, so that path stays untested here.

Add a subtest that builds the app with fiber.New(fiber.Config{DisableHeaderNormalizing: true}) and sends a lowercase content-length and a lowercase x-forwarded-for. That case proves clearCopiedHeaders skips framing fields by fold-equality and that Del matches the raw stored spelling.

I can write this subtest if you want it.

🤖 Prompt for 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.

In `@middleware/adaptor/adaptor_test.go` around lines 2300 - 2377, Add a subtest
to Test_HTTPMiddleware_HeaderFidelity using
fiber.New(fiber.Config{DisableHeaderNormalizing: true}) and lowercase
content-length and x-forwarded-for request headers. Exercise the existing
middleware flow and assert framing headers remain correctly handled while the
lowercase forwarded-for value is preserved or replaced through
raw-spelling-aware deletion. Keep the test focused on clearCopiedHeaders and
isFramingHeader behavior without changing the existing canonical-header
subtests.
docs/middleware/cache.md (1)

127-211: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Run the Markdown lint task for these new sections.

The repository requires make markdown after Markdown changes. The added sections are long and contain hard-wrapped prose plus a fenced Go block, which are common lint targets.

As per coding guidelines: "**/*.md: Run make markdown to lint all Markdown files when modifying code".

🤖 Prompt for 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.

In `@docs/middleware/cache.md` around lines 127 - 211, Run the repository’s
Markdown lint task with make markdown after updating the new sections in the
cache middleware documentation, then fix any reported formatting issues in the
hard-wrapped prose or fenced Go example.

Source: Coding guidelines

middleware/cache/cache.go (1)

770-780: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the always-true v >= 0 check.

fasthttp.ParseUint returns a non-negative int when err == nil. The inner condition never fails, so it only adds a branch.

♻️ Proposed simplification
 		if b := fieldname.First(&c.Response().Header, fiber.HeaderAge, canonical); len(b) > 0 {
 			if v, err := fasthttp.ParseUint(b); err == nil {
-				if v >= 0 {
-					ageVal = uint64(v)
-				}
+				ageVal = uint64(v)
 			}
 		} else {
🤖 Prompt for 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.

In `@middleware/cache/cache.go` around lines 770 - 780, Remove the redundant v >=
0 condition in the ageVal assignment within the response-header age parsing
logic; when fasthttp.ParseUint succeeds, assign v directly to ageVal, while
preserving the existing error handling and default header behavior.
middleware/cache/utils.go (1)

156-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the constant.

maxIgnoredHeaderLen is 32, but the longest name in ignoreHeaders is Proxy-Authorization (19 bytes). The constant is an upper bound, not the longest name. State it as a bound so a later reader does not shrink it to match the current list.

🤖 Prompt for 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.

In `@middleware/cache/utils.go` around lines 156 - 158, Update the comment above
maxIgnoredHeaderLen to describe 32 as an upper bound for ignored header-name
lengths, not as the longest current name in ignoreHeaders; retain the constant
value and its folding behavior unchanged.
🤖 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/extra/internal.md`:
- Around line 367-368: Update the cookie security description in “Setting a
Cookie” to limit protection from plaintext transport to cases where the Secure
attribute is set. Clarify that HTTP-originated requests may receive a non-Secure
fiber_flash cookie and therefore may transmit it over HTTP, while retaining the
existing HttpOnly and visibility statements.

In `@extractors/extractors.go`:
- Around line 348-351: Update FromAuthHeader to retrieve the authorization
header through headerlookup.Value instead of byte-exact c.Get, preserving the
existing scheme validation and authentication flow. Add coverage confirming
FromAuthHeader accepts differently cased authorization field names when header
normalization is disabled.

---

Nitpick comments:
In `@docs/middleware/cache.md`:
- Around line 127-211: Run the repository’s Markdown lint task with make
markdown after updating the new sections in the cache middleware documentation,
then fix any reported formatting issues in the hard-wrapped prose or fenced Go
example.

In `@middleware/adaptor/adaptor_test.go`:
- Around line 2300-2377: Add a subtest to Test_HTTPMiddleware_HeaderFidelity
using fiber.New(fiber.Config{DisableHeaderNormalizing: true}) and lowercase
content-length and x-forwarded-for request headers. Exercise the existing
middleware flow and assert framing headers remain correctly handled while the
lowercase forwarded-for value is preserved or replaced through
raw-spelling-aware deletion. Keep the test focused on clearCopiedHeaders and
isFramingHeader behavior without changing the existing canonical-header
subtests.

In `@middleware/cache/cache.go`:
- Around line 770-780: Remove the redundant v >= 0 condition in the ageVal
assignment within the response-header age parsing logic; when fasthttp.ParseUint
succeeds, assign v directly to ageVal, while preserving the existing error
handling and default header behavior.

In `@middleware/cache/utils.go`:
- Around line 156-158: Update the comment above maxIgnoredHeaderLen to describe
32 as an upper bound for ignored header-name lengths, not as the longest current
name in ignoreHeaders; retain the constant value and its folding behavior
unchanged.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c3909ac7-7dea-4592-bfd7-79986a40e80a

📥 Commits

Reviewing files that changed from the base of the PR and between a367aa9 and ddc5560.

📒 Files selected for processing (44)
  • bind.go
  • binder/form.go
  • binder/form_test.go
  • ctx_interface_gen.go
  • ctx_test.go
  • docs/api/ctx.md
  • docs/extra/internal.md
  • docs/middleware/cache.md
  • docs/middleware/logger.md
  • docs/middleware/proxy.md
  • extractors/extractors.go
  • extractors/extractors_test.go
  • helpers.go
  • helpers_test.go
  • internal/crosshost/crosshost.go
  • internal/fieldname/fieldname.go
  • internal/headerlookup/headerlookup.go
  • internal/mediatype/mediatype.go
  • internal/mediatype/mediatype_test.go
  • middleware/adaptor/adaptor.go
  • middleware/adaptor/adaptor_test.go
  • middleware/cache/cache.go
  • middleware/cache/cache_security_test.go
  • middleware/cache/cache_test.go
  • middleware/cache/cachecontrol.go
  • middleware/cache/coverage_test.go
  • middleware/cache/keygen.go
  • middleware/cache/keygen_test.go
  • middleware/cache/utils.go
  • middleware/cache/vary.go
  • middleware/csrf/csrf.go
  • middleware/csrf/csrf_test.go
  • middleware/logger/context_tag.go
  • middleware/logger/logger_test.go
  • middleware/logger/tags.go
  • middleware/proxy/bench_test.go
  • middleware/proxy/fuzz_test.go
  • middleware/proxy/proxy.go
  • middleware/proxy/proxy_test.go
  • middleware/proxy/security.go
  • middleware/proxy/security_test.go
  • redirect.go
  • req.go
  • req_interface_gen.go
💤 Files with no reviewable changes (1)
  • helpers_test.go

Comment thread docs/extra/internal.md Outdated
Comment thread extractors/extractors.go Outdated
claude and others added 8 commits August 7, 2026 05:58
FromHeader was switched to the case-insensitive read and FromAuthHeader
was missed — the same bug, one function over, on the path every keyauth
and bearer-token caller takes.

Under DisableHeaderNormalizing a lower-case "authorization:" read as
absent, so the scheme check never ran and the request was refused for
carrying no credential when it carried one. Lower case is what HTTP/2 and
HTTP/3 put on the wire, so it is what a front end translating down to
HTTP/1.1 preserves.

Mutation-verified: with the read reverted to c.Get, the new
Test_FromAuthHeader_IgnoresHeaderNameCase fails on the "authorization"
spelling under normalize=false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
client: a redirect that changed hosts kept going. 301, 302 and 303 now
turn any body-carrying method into a GET and drop the body, which is
net/http's redirectBehavior — for every method rather than just POST,
since Fiber drives QUERY requests through the same loop and one that kept
its method would replay its body to the redirect target under a method
that no longer describes it. 307 and 308 are untouched by design.

client cookie jar: Set-Cookie was credited to the URI the caller asked
for, not the one that answered, so the last hop of a redirect chain could
plant cookies for an unrelated origin. The jar now keys them by the
responding URI. A cookie set without a Path is scoped to the directory of
the request that set it, and a path that cannot round-trip through
fasthttp's normalization falls back to "/" rather than to a scope the
cookie could never match — RFC 6265 §8.5 is explicit that the path
attribute is not a security boundary.

res.JSONP: the callback name landed verbatim in a same-origin
text/javascript body, and callers routinely take it straight from the
query string, which is what JSONP is for. It is now reduced to a
JavaScript member expression — everything outside [A-Za-z0-9_$.[]] is
dropped, and the result must still parse as one, so "1.2.3", "[]" and "a["
fall back to the default name rather than emitting a body that throws.

listen: TLSConfig silently supersedes CertClientFile, so a deployment that
set both came up serving every client and demanding a certificate from
none. Every superseded TLS field is now named in a startup warning, and
App.Listener says outright that it serves the listener as supplied — no
TLS and no client-certificate verification unless the caller wrapped it.

Config.SkipUnmatchedRoutes documents that a rate limiter is in the same
position as the rest of the middleware chain: requests to unregistered
paths are neither counted nor throttled.

Several plausible findings were refuted by measurement rather than
patched, and are recorded here so they are not re-investigated: a client
cross-host credential leak (fasthttp already strips them), Host-header
injection through BaseURL (fasthttp answers "Host: //evil.com" with 400
before a handler runs), and a suspected cross-session cache collapse (0
wrong answers across KeyHeaders and Vary, normalizing and not).

Split out of #4581, the last of three. Together the three reproduce that
branch exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
standardClientTransport and hostClientTransport delegated to fasthttp's
redirect loop, so ErrRedirectDowngrade never fired for the client New()
builds. A same-host HTTPS to HTTP hop kept Authorization and Cookie and
sent them in plaintext. Both now use doRedirectsWithClient, which every
transport already satisfies.

301, 302 and 303 drop the request body even where the method already was
GET or HEAD, as net/http's redirectBehavior does. It would otherwise be
replayed to the redirect target, possibly a different host.

A JSONP bracket index that opens with a digit is that number alone, so
cb[0foo], ns[0x] and ns[1.2.3] fall back to the default name rather than
emitting a body that throws.

TLSMinVersion joins the superseded-field warnings the docs already list
it in, and both client-certificate warnings now name ClientAuth's require
modes instead of claiming ClientCAs is jointly required.

The log-capturing listen tests reuse withCapturedLogOutput so they
restore the writer that was active before, and stay serial: Go runs no
parallel test while a serial one holds the package logger, and
Test_Listen_TLSConfig_WithTLSConfigFunc is parallel and warns.
The two remaining ListenConfig comments still read as though ClientCAs
were jointly required. ClientAuth is what requires a certificate;
ClientCAs are the roots it verifies against. Listen also clones the
supplied config, so later mutations of the caller's do not reach it.
isJSONPMemberExpression asked whether parsing was inside brackets, not
whether the token now beginning was opened by one, so "cb[a.0]" passed:
depth is 1 at the digit, but "0" there is a property named after a dot,
which is as illegal as a leading digit at the top level. The sanitizer
returned it unchanged and emitted a body that throws — the outcome the
member-expression check exists to replace with the default name.

Track the '[' instead: inIndex is set only by the bracket that opens a
token and cleared as soon as the token starts, so the number rule
applies where a number may stand and nowhere else. "cb[0]" and "cb[a.b]"
are unaffected; "cb[a.0]", "cb[x[a.1]]" and "cb[a.b.0]" now fall back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
…alues whole

Three review findings from #4585.

cache: the Authorization key hashed a single Peek, so two credentials sent
on separate field lines shared a key with the first alone, and one client's
response replayed for another. Hash the length-prefixed field lines instead
of a comma join, since "Bearer a,Bearer b" on one line and two lines
carrying one each are different principals.

cache: an entry written before Set-Cookie joined ignoreHeaders carries a
cookie beside a body personalized for whoever caused that miss. Dropping
the cookie on replay leaves that body, so treat such an entry as private
and re-run the handler.

mediatype: a quoted-pair inside a parameter value ended the quoted-string
early, folding the rest as parameter names. fasthttp matches "boundary"
case-sensitively and takes the first, so lowercasing a decoy hidden in a
value promoted it over the real one: on `X="\"; BOUNDARY=bogus";
BOUNDARY=Real` the parsed boundary was bogus.

docs: ${ips} logs the chain as supplied whether or not the peer is trusted,
and the flash cookie is Secure only when the request arrived over TLS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
… read

The JSONP validator accepted "for" and "cb[new]" as member expressions, so
the emitted body was a syntax error the browser dropped and the callback
never ran. Refuse a reserved word only where JavaScript reads the token as
a name — the head of the expression and the head inside each index — since
"cb.new" and "cb[a.class]" are property names, which any word may be.

Extract the "default" Format sentinel into a constant so the keyword list
does not trip goconst on an unrelated literal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c36bcecae1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread middleware/cache/cache.go
Comment thread middleware/adaptor/adaptor.go Outdated
Comment thread docs/middleware/cache.md Outdated
claude added 3 commits August 10, 2026 15:06
…on edits

cache: the response was read with the request's normalization setting, but a
proxy hands c.Response() to an outbound fasthttp.Client, which stamps its own
DisableHeaderNamesNormalizing on the response it parses into. A default
app therefore held a lower-case "cache-control: private" that was read as
absent. Confirmed end to end: alice's body was cached and replayed to bob
under a synthesized "public, max-age=300".

Ask the stored names instead of the app config. fieldname.Canonical is one
pass over them and answers exactly what the byte-exact Peek needs to know.

adaptor: Connection left framingHeaders. It frames the hop, not the body, and
naming which fields are hop-by-hop is something wrapped middleware rewrites —
excluding it dropped that edit and left the handler reading the client's own
list. fasthttp re-derives its close flag when the line is written back.

docs: list the whole set a cache entry keeps without StoreResponseHeaders,
which is more than Location and Content-Type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cfe6cb9b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread middleware/adaptor/adaptor.go Outdated
claude added 3 commits August 10, 2026 19:12
…lback

A JSONP body is fetched by a script tag, so it is parsed under the script
goal in sloppy mode — and two words on the rejection list are ordinary
identifiers there. "await" is reserved only inside a module or an async
function, "yield" only in strict mode or a generator, so "await.cb",
"cb[await]", "yield.cb" and "cb[yield]" all name a callback that a browser
would have invoked, and each fell back to "callback" instead.

Checked the whole list against V8 rather than fixing the one that was
reported: every listed word was compiled in both positions under the script
goal, and exactly these two parse. The rest are syntax errors wherever they
stand, and "let" stays contextual in its own third way, handled where it is
read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
Joining the rewritten values fixed the lost token but broke the close
signal: fasthttp's Set clears connectionClose for anything but a bare
"close", so middleware writing ["close", "X-Internal"] left the server
treating as reusable a connection the client had asked to close. The test
added with that change codified the wrong result.

fasthttp holds one or the other, never both — once the flag is set, Peek
answers "close" and the stored value is unreachable. Where the two conflict
the flag wins: the server reads it on every request, and keeping a
connection open against the client's instruction is a protocol violation,
where the tokens it hides only describe what is hop-by-hop. Matched
case-insensitively across the token list, per RFC 9110 Section 7.6.1.

The reported case is untouched by the choice: ["keep-alive", "X-Internal"]
carries no close token and still reaches the handler whole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19949ed8a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/headerlookup/headerlookup.go Outdated
claude added 3 commits August 10, 2026 19:49
Peek reports the first line stored under a name whether or not it holds
anything, and an empty answer is indistinguishable from an absent header.
A message carrying "Origin:" ahead of its real Origin therefore read as
having none — and an absent Origin is not a failure to the CSRF check: on
a plaintext request it skips the check and allows the request. Confirmed
end to end, under both spellings and both configurations:

  Origin:                        -> Peek "" , PeekAll ["", "http://evil.example"]
  Origin: http://evil.example

Reported for DisableHeaderNormalizing, where the second line is spelled
"origin:". It is not limited to that: with names normalized both lines
land under "Origin" and the first one still shadows the second, so the
hole was open on the default configuration too.

First now steps past empty lines on both paths. A field line that is
present and empty says nothing a caller can act on, so nothing is lost by
looking past it for one that does; Lines already read every line for the
same reason. The walk is skipped where Peek answers nil, which fasthttp
means as absent rather than empty — Test_First_EmptyLineIsNotNil pins
that distinction so a change upstream fails loudly instead of quietly
reverting this to reading one line.

Measured on Benchmark_Middleware_CSRF_Check, interleaved runs: 314 B/op
and 11 allocs/op unchanged, ns/op +2% median. The allocation-free result
needed the fold walk split into its own function — All's iterator is
heap-allocated where the compiler cannot see the branch is dead, which
cost the canonical path one 24 B allocation per absent lookup. That one
predates this change, so the canonical path is now cheaper than it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
…opped

fasthttp's own redirect loop clears postArgs and the parsed-form flag
together; this loop clears only the values, because the flag is not
reachable from outside the package. parsedPostArgs is unexported and set
false in exactly two places, Request.Reset and Request.ReadLimitBody —
Reset would also take the URI, headers and TLS flag the loop is still
using, and ReadLimitBody overwrites the header from a reader.

The values are the load-bearing half and stay cleared: Request.Write
falls back to the parsed form whenever the body is empty, so without it a
caller that had read PostArgs sent the dropped "a=1" as the body of the
redirected GET.

What is left is confined to a caller that reuses the request without
resetting it. Measured, reading PostArgs after setting a new body:

  untouched fasthttp request   "a=1"   the previous body's values
  after dropRequestBody        ""      no values
  fasthttp's own loop          "b=2"   re-parsed from the new body

So the gap is against fasthttp's loop, not against leaving the request
alone — an untouched request is the one that hands back stale form
values. The body on the wire is the new one in every case.

Test_DropRequestBody_ReusedRequestSendsTheNewBody covers the two
properties that hold: a reused request sends the body it was given, and
the earlier form comes back neither in it nor from a later PostArgs.
Dropping the reset fails it, and the test beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dcc39c66b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/fieldname/fieldname.go
claude added 2 commits August 10, 2026 20:19
…cking one

Reading past an empty line let whoever wrote the other one decide what a
field holds. Under DisableHeaderNormalizing a middleware that clears
Authorization writes the canonical name byte-exactly and leaves the
client's lower-case line — the spelling HTTP/2 and 3 send — beside it.
Both orders fasthttp stores, and what each read answers:

  [Authorization: "", authorization: Bearer client]   first: ""       non-empty: client
  [authorization: Bearer client, Authorization: ""]   first: client   non-empty: client

So the previous read was wrong in the first row and the read before it
was wrong in the second: whichever rule is chosen, one of the two orders
hands the extractor a credential the middleware had just cleared. The
second row was already wrong before this branch, which is the more
common one — a client sending only the lower-case name puts the cleared
line last.

Origin, Referer, Sec-Fetch-Site and Authorization are all defined as a
single value, and RFC 9110 Section 5.2 says a sender must not generate a
second line for such a field. A message carrying two is malformed, and
what it means is not a question with an answer; headerlookup.Value now
says so with a second return rather than resolving it.

Every caller refuses rather than treating that as an absent header. The
distinction is load-bearing in the CSRF check: an absent Origin is not a
failure there, it skips the check on a plaintext request, so mapping
ambiguity to "not found" would hand a malformed message the same pass
this branch just closed. In the extractors it is belt-and-braces — the
ambiguous value is empty and both paths end at ErrNotFound — kept so the
refusal does not depend on that coincidence.

A repeated Origin is now refused even when both lines agree with the
host. That is deliberate: the check cannot know which line the request
meant, and no browser sends two.

Interleaved runs of Benchmark_Middleware_CSRF_Check, 12 samples each,
against the commit before the empty-line fix:

  before      2214 ns/op   314 B/op   11 allocs/op
  empty-line  2270 (+2.6%) 314        11
  this        2146 (-3.0%) 314        11

Counting the lines needs the whole walk, so Ctx.Get's byte-exact peek
ahead of it was redundant and is gone; one PeekAll now answers both the
count and the value, which is a scan fewer than either earlier shape.
Immutable is honored where Ctx.Get honored it.

Three mutations fail: dropping the count on either path, and mapping the
CSRF ambiguity back to errOriginNotFound. Ignoring the flag in the
extractor does not fail, which is the belt-and-braces point above rather
than a gap in coverage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8292a47de9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extractors/extractors.go Outdated
claude added 2 commits August 10, 2026 20:37
…omHeader

FromHeader is given its name by the application, so the strict
single-value read was wrong for it. Accept, Forwarded and the other list
fields may legally arrive on several lines, and there two lines are one
value rather than two answers — refusing them turned a valid request into
ErrNotFound.

They are joined the way RFC 9110 Section 5.3 says a recipient may, in the
order received. Nothing is loosened where the named field is a credential:
a token joined with another does not match the one that was issued, so the
comparison the caller makes still fails, which is where Value's refusal
lands directly.

FromAuthHeader keeps Value. Authorization is named by this package rather
than by config, and it is a single-value field.

Also covers the two refusals the previous commit left untested: a repeated
Sec-Fetch-Site and a repeated Referer, the second reachable only on HTTPS,
which needs the trusted-proxy app since Ctx.Scheme reads X-Forwarded-Proto
only from a trusted peer. Both csrf functions and originMatchesHost are at
100% now, against 88.9% and 93.8%.

Reverting FromHeader to the strict read fails
Test_Extractor_FromHeader_CombinesRepeatedLines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2c6add953

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/headerlookup/headerlookup.go
Comment thread middleware/cache/cache.go
claude and others added 4 commits August 10, 2026 20:55
…directive

The Cache-Control scan split on every comma, so the tokens inside a
quoted extension value were read as directives of their own. RFC 9111
Section 5.2 makes that value a quoted-string, and a comma in one is data.

Reproduced end to end: a handler answering with

  Cache-Control: ext="a, public, b"
  Set-Cookie:    session=client-1

was read as having said "public", which is enough to pass the gate that
lets a cookie-setting response be stored. Its body was then replayed to
later clients with only the cookie dropped. `ext="a, s-maxage=99, b"`
passes the same gate, and `ext="a\", public, b"` gets there through the
escaped quote.

The part scan now tracks the quoted-string and steps over a quoted-pair.
Unterminated quotes swallow the rest of the field, which is the safe way
round: what follows is read as one value rather than as directives a
sender never separated.

hasDirective has the same blindness and is left alone: both its callers
ask about no-cache and private, where reading a quoted token as a
directive only makes the cache more conservative.

Also fixes FromHeader named with Cookie, from the previous commit's join.
Cookie is not comma-separated — RFC 6265 Section 5.4 joins its crumbs
with "; " — and fasthttp keeps them in a store of its own that PeekAll
enumerates one at a time, so a wire request carrying Cookie twice came
back as "a=1, b=2". Collecting the store first, as the cache's
keyFieldLines already does, makes fasthttp reassemble the field.

Test_Cache_QuotedDirectiveDoesNotAuthorizeSharing covers the three
values above, with Test_Cache_QuotedDirectiveStillReadsRealOnes as the
control that public outside the quotes still authorizes sharing.
Test_Extractor_FromHeader_CookieKeepsItsOwnSeparator reads from the wire,
which is the only shape where the crumbs arrive separately. Reverting
either fix fails its tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
…ends it

Request.URI swallows the parse error and hands back a half-parsed URI,
the same way UpdateBytes does for redirect targets. Measured:

  http://example.com:abc/x  ->  URI().Host="example.com:abc" path="/"
                                URI.Parse: invalid port ":abc" after host

So the malformed authority stayed as the host and the path was lost. A
HostClient dials its fixed Addr whatever the URI says, so that host went
out as the Host header of a request the caller never wrote.

The targets this loop composes have been checked since they are read;
this is the same check for the URI the caller supplied. It sits inside
the loop after SetRequestURI, where fasthttp's own doRequestFollowRedirects
calls the error-returning req.parseURI, so the check covers every URI the
loop is about to send rather than only the first.

Test_Transport_DoRedirects_RefusesUnparsableInitialURI asserts the error
and that nothing was sent, with a URI that parses as the control.
Removing the check fails both malformed rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZwaNA4CeVV9KxKhBDXWGC
🐛 fix: client redirect handling, TLS diagnostics and the JSONP callback
@ReneWerner87 ReneWerner87 added this to v3 Aug 11, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Aug 11, 2026
@ReneWerner87
ReneWerner87 merged commit 92144fc into claude/split-1-url-composition Aug 11, 2026
27 checks passed
@ReneWerner87
ReneWerner87 deleted the claude/split-2-header-matching branch August 11, 2026 08:37
@github-project-automation github-project-automation Bot moved this to Done in v3 Aug 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef43f3f24d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread client/transport.go
// against the initial host, ignoring the port, subdomains still trusted.
if !trustedRedirectTarget(nextHost, initialHostname) {
for _, h := range crosshost.SensitiveHeaders {
req.Header.Del(h)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Delete cross-host credentials case-insensitively

When callers use req.Header.DisableNormalizing() and add a lower-case credential such as authorization or cookie, RequestHeader.Del compares the stored name byte-for-byte, so this loop leaves the credential in place and sends it to an unrelated host selected by the redirect response. Use the case-insensitive deletion helper for every sensitive name so the cross-host protection also covers non-normalized requests.

AGENTS.md reference: AGENTS.md:L11-L13

Useful? React with 👍 / 👎.

Comment thread listen_test.go
}

// go test -run Test_Listen_TLSConfig_WarnsSupersededFields
func Test_Listen_TLSConfig_WarnsSupersededFields(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make the new log-capture tests parallel-safe

This test and the other newly added warning tests intentionally omit t.Parallel() because they mutate package-level logging state, but the repository requires every new test and subtest to invoke it. Refactor the warning/log capture path to isolate or inject the logger, then run these tests and their subtests in parallel rather than adding serial exceptions.

AGENTS.md reference: AGENTS.md:L9-L9

Useful? React with 👍 / 👎.

Comment thread client/transport.go
// A 100-continue expectation may not be generated without content
// (RFC 9110 Section 10.1.1), and a strict target answers the bodyless
// follow-up with 417 rather than the redirect the caller was following.
req.Header.Del(fasthttp.HeaderExpect)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove non-normalized Expect headers with redirected bodies

For a request whose header normalization is disabled and whose field is stored as lower-case expect, this byte-exact deletion misses it. After a 301, 302, or 303, dropRequestBody therefore emits a bodyless redirected request that still carries Expect: 100-continue; as the adjacent comment notes, a strict target can answer 417 instead of completing the redirect. Delete this field case-insensitively, along with the other body metadata removed above.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants