Skip to content

🐛 bug: rank wildcards behind character classes in redirect ordering - #4607

Merged
ReneWerner87 merged 10 commits into
mainfrom
propose-fix-for-redirect-precedence-bug
Aug 12, 2026
Merged

ReneWerner87 merged 10 commits into
mainfrom
propose-fix-for-redirect-precedence-bug

Conversation

@gaby

@gaby gaby commented Aug 11, 2026

Copy link
Copy Markdown
Member

Motivation

  • The redirect ordering tie-breaker let a broad wildcard rule like /api/* shadow a narrower character-class rule such as /api/[ab] when their literal prefix and pinned lengths tied, so a request could be handled by the catch-all instead of the specific rule meant for it.

Description

  • Rank the wildcard on a key of its own, between the pinned-length comparison and the width: wildcardRank returns 1 for a rule carrying Fiber's * and 0 otherwise, so the wildcard rule sorts second.
  • It is ranked rather than counted because a wildcard matches a run of any length, which no number the widths are compared against can stand for — a width saturates at maxPatternWidth, and two saturated rules tie again. Keeping it out of the width also leaves the width free to go on separating two rules that both carry one, so /p/*[ab] still beats /p/*[a-z].
  • wildcardRank skips character classes and \Q ... \E spans, since New expands every * before compiling and the result is a class ([(.*)]) or literal text rather than a live wildcard.

The pattern scanner is untouched.

Files changed: middleware/redirect/redirect.go, middleware/redirect/redirect_test.go.

Testing

  • go test ./middleware/redirect -count=1 passes, as does GOARCH=386 go test ./middleware/redirect -count=1; go vet ./middleware/redirect and gofmt -l are clean.
  • Three cases in Test_Redirect_RuleOrderIsBySpecificity: the class outranking the wildcard on /api/a (query preserved), the wildcard still taking /api/z, and /p/*[ab] beating /p/*[a-z] on /p/za. The first fails without the new key.
  • Unit assertions in Test_Redirect_NestedAlternationLosesTheTieBreak pin wildcardRank for a bare star, a class, a quoted star, an unterminated \Q, [*], and a star after an escape. wildcardRank is at 100% coverage.

Note on scope

An earlier revision of this branch also reworked the pattern scanner — a POSIX class size table, an overflow-safe multiply, quoted-span handling inside width(), a clamp for classes whose members overlap, and a case for a star behind a backslash. Each addressed a real construct raised in review, but all of them were fixing pre-existing imprecision in how the scanner measures rules rather than the ordering bug this PR is for, and none was reachable from the reported issue. They have been reverted to keep this change to its subject. The measurement gaps are still there, unchanged from before this branch, and are better handled on their own.


Codex Task

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Redirect rule ordering now ranks Fiber wildcard patterns after comparable non-wildcard patterns. Pattern-width scanning handles quoted spans, dot wildcards, and overflow-safe multiplication. Tests cover ordering, quoted stars, fallback matching, and width limits.

Changes

Redirect wildcard ordering

Layer / File(s) Summary
Pattern-width scanning and overflow handling
middleware/redirect/redirect.go
Pattern-width scanning recognizes wildcard and quoted spans. It counts . as a 256-way wildcard and clamps multiplication safely. Rule sorting places comparable wildcard patterns after non-wildcard patterns.
Rule-ordering and width validation
middleware/redirect/redirect_test.go
Tests cover character-class precedence, wildcard fallback, specificity, query preservation, quoted stars, unterminated quotes, and width saturation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: sixcolors, renewerner87, efectn

Poem

A rabbit checks each redirect trail,
Quoted stars remain literal.
Wildcards wait behind precise paths,
Safe widths stay within bounds.
Tests hop through every case.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.71% 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.
Title check ✅ Passed The title clearly describes the main change: ranking wildcard redirect rules behind narrower character-class rules.
Description check ✅ Passed The description explains the bug, implementation, tests, scope, and benefits, although it does not complete every template section.
✨ 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 propose-fix-for-redirect-precedence-bug

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.

@ReneWerner87 ReneWerner87 added this to v3 Aug 11, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Aug 11, 2026
@gaby gaby removed the aardvark label Aug 11, 2026
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.67%. Comparing base (9da941f) to head (c7679c5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4607      +/-   ##
==========================================
- Coverage   93.69%   93.67%   -0.03%     
==========================================
  Files         140      140              
  Lines       15982    16004      +22     
==========================================
+ Hits        14975    14991      +16     
- Misses        624      629       +5     
- Partials      383      384       +1     
Flag Coverage Δ
unittests 93.67% <100.00%> (-0.03%) ⬇️

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.

@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: 02c72b2e0c

ℹ️ 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/redirect/redirect.go Outdated
Comment thread middleware/redirect/redirect.go Outdated
Comment thread middleware/redirect/redirect.go Outdated
claude added 2 commits August 11, 2026 15:00
Saturating a wildcard rule at maxPatternWidth erased the width of every
construct after it, so "/p/*[ab]" tied "/p/*[a-z]" and lost the tie-break to
key order. It also fed the clamp value into the group multiplication, which
overflows an int on a 32-bit build and sorts the resulting negative first.

Fiber expands "*" to "(.*)", so measure it as the widest single position, the
same as "." — that still ranks a catch-all behind a character class pinning
the same prefix, while what follows the wildcard keeps separating two rules
that both open with one. Multiply through a saturating helper so a nest of
groups cannot overflow, and skip "\Q ... \E" spans, where a star names itself
rather than opening a wildcard.
Go's parser quotes to the end of the pattern when the "\E" is missing, so the
scanner has to stop there too.

@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: 8de760b067

ℹ️ 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/redirect/redirect.go Outdated
claude added 2 commits August 11, 2026 15:13
A width saturates, so counting the wildcard among the multipliers pushed wide
rules into the clamp 256x sooner: "/p/*[a-z][ab]x11" and "/p/*[ab]x12" both
reached maxPatternWidth and tied, and key order handed a shared path to the
broader rule.

A wildcard matches a run of any length, which no number the widths are compared
against can stand for, so rank it ahead of the width instead. Two rules that
both carry one are then separated by everything beside it, which is the figure
the width already computes: 53248 against 4096 for the pair above.
The inline branch pair only took its "wildcard sorts second" arm when map
iteration happened to hand the wildcard rule in as the left operand, so whether
it ran varied per run. A rank of 0 or 1 reads like the keys around it and is
asserted directly instead.

@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: 4b021114ee

ℹ️ 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/redirect/redirect.go
Comment thread middleware/redirect/redirect.go
Comment thread middleware/redirect/redirect.go
classWidth stopped at the "]" closing "[:digit:]", which does not close the
class holding it, so the scan resumed inside the brackets and measured the
members beyond as pattern syntax. With the wildcard now ranked ahead of the
width, that also had a "*" among those members read as Fiber's wildcard, and
"/p/[[:digit:]*]" — an exact one-character rule — sorted behind the "/p/."
catch-all it should outrank.

@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: e1dfec7a7d

ℹ️ 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/redirect/redirect.go Outdated
Comment thread middleware/redirect/redirect.go
Two rules were still ordered by what they look like rather than what they
compile to.

A POSIX name counted as one member, so "[[:digit:]]" scored narrower than the
"[09]" it contains and shadowed it. Measure the name by the bytes it matches,
negation included.

The replacement runs over the whole key, so a backslash does not spare the star
that follows it: "\*" becomes "\(.*)", an escaped parenthesis and then a live
wildcard. Read as a literal star, "/p/(\*" ranked as pinning every position
while it matched any suffix at all.

@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: 15e8f7d417

ℹ️ 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/redirect/redirect.go Outdated
claude added 2 commits August 11, 2026 15:56
classWidth sums its members rather than unioning them, so overlapping sets are
counted twice and the total can run past the 256 bytes a class is drawn from.
Past that the sum says nothing about the count, and taking a complement from it
came out negative: "[^[:alpha:][:^digit:]]" matches the ten digits, scored
256-298, and floored to 1 it sorted ahead of the "[09]" it contains.

Read such a class as matching every byte instead, so one this scan cannot
measure never shadows one it can. That also caps a non-negated class at the 256
it could match at most.
The scanner changes that grew around this fix — a POSIX size table, an
overflow-safe multiply, quoted-span handling in width(), a clamp for classes
whose members overlap, a case for the star behind a backslash — were each
chasing a construct review turned up, not the bug this PR is for. They touched
measurement code that was already imprecise about those same constructs before
this branch, so they were fixing the scanner rather than the ordering.

What is left is the one thing the bug needed: rank a rule carrying a wildcard
behind one that does not, and leave the width to separate two rules that both
carry one. The scanner is untouched.
@ReneWerner87
ReneWerner87 merged commit baab347 into main Aug 12, 2026
29 checks passed
@ReneWerner87
ReneWerner87 deleted the propose-fix-for-redirect-precedence-bug branch August 12, 2026 14:52
@github-project-automation github-project-automation Bot moved this to Done in v3 Aug 12, 2026
@ReneWerner87 ReneWerner87 modified the milestones: v3, v3.5.0 Aug 13, 2026
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