Skip to content

fix: answer an invalid OriginPatterns entry with 500 - #596

Draft
mafredri wants to merge 11 commits into
mafredri/accept-error-handlerfrom
mafredri/origin-patterns-check-all
Draft

mafredri wants to merge 11 commits into
mafredri/accept-error-handlerfrom
mafredri/origin-patterns-check-all

Conversation

@mafredri

@mafredri mafredri commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

An invalid OriginPatterns entry, such as "[", is a server misconfiguration, but Accept rejected the request with 403 Forbidden, which tells the client that its origin is not allowed.

Accept now rejects it with 500 Internal Server Error and an error that wraps path.ErrBadPattern. The default body is "Internal Server Error". As before, Accept tries the patterns in order and reports an invalid pattern only when it tries it, so same origin requests, requests without Origin, and origins matched by an earlier pattern are accepted. The OriginPatterns doc now says this and suggests checking patterns with path.Match to find invalid ones up front.

Test_authenticateOrigin covers an invalid pattern with no Origin, with a same origin request, and after a matching pattern. Another test checks that InsecureSkipVerify skips the patterns.

Depends on #588, which makes Accept return the pattern error instead of "Forbidden".

Decision log
  • No up-front check of every pattern. Two implementations added 0.15 to 0.45 µs per handshake with five patterns, and a caller can check its patterns with path.Match before use.
  • The log.Printf of the pattern error stays.

🤖 This PR was created with the help of Coder Agents.

A handler or caller could tell an invalid pattern from a disallowed
origin only through the path.ErrBadPattern wrap, which no doc promised.
An invalid pattern rejected only cross origin requests that reached it
before a match, so a misconfigured server failed for some clients and
worked for others. Accept now checks every pattern first and rejects
every handshake, whatever its origin.
An invalid pattern now rejects every handshake, including same origin
requests, so 403 Forbidden told those clients their origin was not
allowed. The fault is the server's configuration.
Accept does not check the patterns when InsecureSkipVerify is set, so
"every handshake" was false in that mode.
An invalid pattern now rejects same origin requests too. The comment on
the pattern check also restated the loop instead of saying why it runs
before the Origin checks.
The OriginPatterns doc says InsecureSkipVerify skips the pattern check,
and no test set both. The badOriginPattern row also no longer sets an
Origin, which no longer affects the result, so the Accept level tests
check a request without one.
"Every handshake" already covers every origin.
@mafredri

This comment has been minimized.

@coder-agents-review

coder-agents-review Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Chat: Review posted | View chat
Requested: 2026-11-05 19:41 UTC by @mafredri
Spend: $18.53 / $100.00

Review history
  • R1 (2026-11-05): 13 reviewers, 1 Nit, 1 P3, 1 P4, COMMENT. Review
  • R2 (2026-11-05): 13 reviewers, 1 Nit, 2 P3, 3 P4, COMMENT. Review
  • R3 (2026-11-05): 11 reviewers, 1 Nit, 2 P3, 6 P4, COMMENT. Review

deep-review v0.13.0 | Round 3 | 98a34ba..5e45c9c

Last posted: Round 3, 9 findings (2 P3, 6 P4, 1 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 Note Dropped by orchestrator (test pins the InsecureSkipVerify contract stated at accept.go:47; passing on base is expected for unchanged behavior) accept_test.go:211 insecureSkipVerifyIgnoresBadPattern passes on base; guards placement only R1 Netero No
CRF-2 Note Dropped by orchestrator (5e45c9c restores in-order matching, so the log fires only when the loop reaches the invalid entry, as on base) accept.go:134 log.Printf now fires on every handshake with an invalid pattern R1 Netero No
CRF-3 P3 Author fixed (fa62356) accept.go:252 Validation loop comment omits why match(p, "") validates, which the discard at line 279 relies on R1 Gon P2 Yes
CRF-4 P2 Dropped by orchestrator (changed line 36 is accurate; the invalid-pattern exception is stated at lines 47 to 49 of the same doc; rest of the rewrite targets unchanged lines, see CRF-14) accept.go:35 OriginPatterns doc line 36 omits the invalid-pattern exception R1 Gon No
CRF-5 P2 Dropped by orchestrator (comment is accurate after the change; redundancy with the row's fields does not mislead) accept_test.go:338 Test comment restates the row's fields R1 Gon No
CRF-6 Nit Author fixed (6950739) accept_test.go:211 New test name says "bad" where the rest of the PR says "invalid" R1 Gon Yes
CRF-7 P4 Author fixed (3b4d4c1) accept.go:47 Doc says "every handshake" gets 500, but verifyClientRequest failures are rejected first with their own status R1 Leorio Yes
CRF-8 Note Dropped by orchestrator (5e45c9c removes the behavior change for same origin clients; only the status of already rejected requests changes) accept.go:135 Release notes need the behavior change paragraph; same origin clients working on v1.8.15 fail after this R1 Chopper No
CRF-9 OOS Out of scope (body) accept.go:35 OriginPatterns doc does not say requests without Origin are authorized R1 Leorio No
CRF-10 OOS Out of scope (body) accept.go:251 authenticateOrigin parameter originHosts holds patterns, not hosts R1 Gon No
CRF-11 OOS Out of scope (body) accept.go:290 Inverted ranges such as [z-a] pass path.Match validation and match nothing R1 Chopper No
CRF-12 OOS Out of scope (body) accept_test.go:197 closeRace wg.Add/Done blocks forever on panic; sync.WaitGroup.Go replaces it R1 Ging-Go No
CRF-13 OOS Out of scope (body) accept_test.go:699 tc := tc copies unnecessary under go 1.26 (also line 830) R1 Netero No
CRF-14 OOS Out of scope (body) accept.go:39 Unchanged OriginPatterns doc lines pad the contract ("i.e", repeated path.Match link, "ramifications" sentence) R1 Gon No
CRF-15 OOS Out of scope (body) accept_test.go:339 badOriginPattern uses "bad" for the condition new tests call "invalid" R1 Gon No
CRF-16 P3 Author fixed (5e45c9c, check removed) accept.go:258 Loop comment says "name" for target and omits that the loop must not stop at the first match R2 Gon P2, Leorio P4 Yes
CRF-17 P4 Open (re-raised R3: new figure 0.15 to 0.45 µs omits fa62356 at about 0.95 µs and names no pattern set or request kind) accept.go:271 Decision log cost figure describes the removed separate check, not the shipped loop R2 Mafu-san P2, Killua Note Yes
CRF-18 P4 Author fixed (title and body rewritten for 5e45c9c) accept.go:47 PR title and Behavior change line still say "every handshake" after the CRF-7 doc fix R2 Mafu-san P3 Yes
CRF-19 Note Dropped by orchestrator (path.Match doc says the error occurs "when pattern is malformed", which supports the author's reading; concerns a reply, not code) accept.go:258 Author reply credits the path.Match doc for a guarantee stated only by the implementation R2 Mafu-san No
CRF-20 OOS Out of scope (body) accept.go:284 Unauthorized-origin error says Origin but prints u.Host, a value the client never sent; line moved, text unchanged R2 Gon No
CRF-21 P4 Open accept.go:47 Doc says patterns are tried in order but not that Accept stops at the first match R3 Mafuuu Yes
CRF-22 P4 Open accept_test.go:822 No test row puts an invalid pattern before a matching one R3 Bisky Yes
CRF-23 P4 Open accept.go:135 Decision log keeps log.Printf without a reason R3 Leorio Yes
CRF-24 Note Note (body) accept.go:50 path.Match on the raw pattern rejects invalid UTF-8 bracket patterns that Accept's lowercased match accepts R3 Mafuuu, Gon No
CRF-25 Note Note (body) accept.go:136 Unmatched cross origin requests that reach an invalid entry change from 403 to 500, visible to 5xx alerting R3 Knov No
CRF-26 Note Dropped by orchestrator (informational; the missing ordering test is CRF-22) accept_test.go:808 invalidPattern* rows pass on base after 5e45c9c R3 Mafu-san No

Contested and acknowledged

Round log

Round 1

Netero first pass: no findings, 2 Notes. Full panel (round 1), security-sensitive origin matching so multi-domain size: ging-go, kurapika, ryosuke, killua, pariston, mafuuu, bisky, hisoka, gon, leorio, chopper, mafu-san, wildcard zoro (random from distilled set). 1 P3, 1 P4, 1 Nit posted; 3 dropped; 2 Notes in body; 7 out of scope. Gon's P2 on the loop comment downgraded to P3 (comment is incomplete, not wrong). Reviewed against 98a34ba..fd4a902.

Round 2

Churn guard PROCEED: CRF-3, CRF-6, CRF-7 author fixed. Restructure round (authenticateOrigin rewritten in fa62356), full panel: netero (no findings), ging-go, kurapika, ryosuke, killua, pariston, mafuuu, bisky, hisoka, gon, leorio, chopper, mafu-san, wildcard razor (random from distilled set). 1 P3, 2 P4 posted; 1 Note dropped; 1 out of scope. CRF-16 settled at P3 between Gon P2 and Leorio P4 because invalidPatternAfterMatch fails if a break is added, so the omission costs a confusing test failure, not a shipped bug. CRF-17 lowered from P2 to P4: the PR text labels the figure as an earlier separate check, and the measured shipped cost (about 0.5 to 1 µs per handshake, Killua and Mafu-san) does not change the decision. CRF-18 lowered from P3 to P4 to match CRF-7, the same claim in the doc. Reviewed against 98a34ba..fa62356.

Round 3

Churn guard PROCEED: CRF-16, CRF-17, CRF-18 addressed by 5e45c9c, which drops the up-front check and changes the PR goal to the 403 to 500 status change only. CRF-2 and CRF-8 dropped because their premise (log and rejection on every handshake) no longer holds. Restructure round (PR title and goal changed), full panel: netero (no findings), ging-go, ryosuke, kurapika, pariston, mafuuu, bisky, gon, leorio, chopper, mafu-san, wildcard knov (random from distilled set). CRF-17 re-raised by Mafu-san at P2 on the rewritten figure; kept at its inventory severity P4 because the decision it supports holds with either number (Mafu-san states this). 3 new P4 posted, 1 re-raise reply, 2 Notes in body, 1 Note dropped. Knov's note that raw path.Match gives the same result as Accept is contradicted by Mafuuu's and Gon's invalid UTF-8 case; the empirical case wins. Reviewed against 98a34ba..5e45c9c.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review 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.

This PR checks every OriginPatterns entry before the Origin checks and answers an invalid entry with 500 on every handshake. Findings: 1 P3, 1 P4, 1 Nit.

Out of scope (needs a ticket or explicit acceptance by a human):

  • accept.go:35: the OriginPatterns doc does not say that a request without an Origin header is authorized, so a reader can assume the patterns restrict non-browser clients.
  • accept.go:39: unchanged lines of the same doc block use "i.e" for an example, repeat the path.Match link, and add a "ramifications" sentence that says nothing the CSRF sentence does not.
  • accept.go:251: the authenticateOrigin parameter originHosts holds patterns, not hosts, while both loops name the element hostPattern.
  • accept.go:290: inverted ranges such as [z-a] pass path.Match without an error and match nothing, so the new check does not report them.
  • accept_test.go:197: closeRace pairs wg.Add(2) with manual wg.Done(), so a panic before Done blocks wg.Wait forever; sync.WaitGroup.Go removes this.
  • accept_test.go:699: the tc := tc copies here and at line 830 are unnecessary under go 1.26.
  • accept_test.go:339: badOriginPattern says "bad" for the condition the new tests call "invalid".

Notes:

  • With an invalid pattern, the log.Printf at accept.go:134 now writes one line per handshake, at the client request rate.
  • The release notes need the behavior change paragraph from the PR description, because same origin clients that connect on v1.8.15 fail after this change.

🤖 This review was automatically generated with Coder Agents.

Comment thread accept.go Resolved
Comment thread accept.go Resolved
Comment thread accept_test.go Resolved
The doc, the error text and the Test_authenticateOrigin cases call the
pattern invalid, not bad.
A request that fails the protocol checks gets its own 4xx before the
pattern check runs, so "every handshake" was too broad.
authenticateOrigin matched every pattern against "" to check it, then
matched the patterns again against the origin. path.Match reports a
malformed pattern whatever the name, so one loop now checks and matches
each pattern and keeps the result.
@mafredri

This comment has been minimized.

@coder-agents-review coder-agents-review 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.

This round reviews fa62356, which merges pattern validation into the matching loop, and the doc and test name fixes. Findings: 1 P3, 2 P4.

The three findings from the previous review are fixed: the comment on the separate match(p, "") check (fa62356 removes that check), the "every handshake" doc wording (3b4d4c1), and the test name (6950739).

Out of scope (needs a ticket or explicit acceptance by a human):

  • accept.go:284: the unauthorized-origin error says request Origin %q but prints u.Host (other.com), not the header the client sent (https://other.com). The diff moved this line without changing its text.

The out-of-scope items and notes from the previous review still apply.

🤖 This review was automatically generated with Coder Agents.

Comment thread accept.go Resolved
Comment thread accept.go Resolved
Comment thread accept.go Resolved
Checking every pattern on each handshake added work to same origin
requests and requests without Origin, and a caller can check its
patterns with path.Match before use. Accept now reports an invalid
pattern only when it tries it, as before, still with 500, and the doc
says how to find invalid patterns up front.
@mafredri mafredri changed the title fix: reject every handshake on an invalid OriginPatterns entry fix: answer an invalid OriginPatterns entry with 500 Nov 5, 2026
@mafredri

mafredri commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

/coder-agents-review

🤖

@coder-agents-review coder-agents-review 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.

This round reviews 5e45c9c, which drops the up-front pattern check and keeps in-order matching, so the PR now changes only the status for an invalid OriginPatterns entry from 403 to 500 and documents the rule. Findings: 3 P4, plus one re-raised P4 on the decision log cost figure.

The three findings from the previous review are addressed: 5e45c9c removes the loop comment and the shipped loop, and the title and body no longer say "every handshake". The rewritten cost figure has the same problem as before; see the reply on that thread.

Notes:

  • accept.go:50: path.Match on the raw pattern rejects a few patterns that Accept accepts, such as "[\xff]", because match lowercases first and strings.ToLower replaces invalid UTF-8 with U+FFFD. The suggested check never misses a pattern that Accept answers with 500.
  • accept.go:136: on v1.8.15, an unmatched cross origin request that reaches an invalid entry gets 403; after this PR it gets 500. The client chooses Origin, so any cross-site page can produce these 500s. The release notes should mention the change for operators with 5xx alerts.

The out-of-scope items from the previous reviews still apply.

🤖 This review was automatically generated with Coder Agents.

Comment thread accept.go
// https://golang.org/pkg/path/#Match). By default, it is matched
// against the request origin host. If the pattern contains a URI
// scheme ("://"), it will be matched against "scheme://host".
// Accept tries the patterns in order. When it tries an invalid pattern,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P4 [CRF-21] The doc says Accept "tries the patterns in order" but not that it stops at the first match, so it does not say which requests never reach an invalid pattern. (Mafuuu)

With OriginPatterns: []string{"good.com", "["}, every handshake from good.com succeeds and only other cross origin handshakes get 500 (invalidPatternAfterMatch pins this). The sentence also reads as "every pattern is tried". An operator who tests only from good.com sees no error and ships the invalid entry. Suggested: "Accept tries the patterns in order and stops at the first match."

🤖

Comment thread accept_test.go
success: true,
},
{
name: "invalidPatternAfterMatch",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P4 [CRF-22] No test puts an invalid pattern before a matching pattern, so the "in order" rule in the new doc is not tested. (Bisky)

invalidPatternAfterMatch covers {"other.com", "["}, and badOriginPattern uses "[" alone. Changing authenticateOrigin to skip invalid patterns and return the error only when no later pattern matches still passes go test -run 'TestAccept|Test_authenticateOrigin'. Under that change, {"[", "other.com"} accepts https://other.com. Add a row invalidPatternBeforeMatch with origin https://other.com, host example.com, patterns {"[", "other.com"}, and success: false.

🤖

Comment thread accept.go
defaultBody := err.Error()
status, defaultBody := http.StatusForbidden, err.Error()
if errors.Is(err, path.ErrBadPattern) {
log.Printf("websocket: %v", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P4 [CRF-23] The decision log says "The log.Printf of the pattern error stays." with no reason, unlike the other entry. (Leorio)

A maintainer who reads the squashed commit cannot tell why the log was kept. A likely reason: the default body is only "Internal Server Error", so for a handler that drops the error from Accept and sets no ErrorHandler, the log line is the only place an operator sees which entry is invalid. State the actual reason.

🤖

This branch has not been deployed

No deployments
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.

1 participant