Skip to content

fix(filter): make the glob walker and its regex agree on trailing slashes - #435

Merged
scttbnsn merged 3 commits into
dev/v2.1from
fix/glob-trailing-slash
Sep 4, 2026
Merged

fix(filter): make the glob walker and its regex agree on trailing slashes#435
scttbnsn merged 3 commits into
dev/v2.1from
fix/glob-trailing-slash

Conversation

@scttbnsn

@scttbnsn scttbnsn commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

The segment-glob fast path and the regex it stands in for disagreed on trailing slashes. The walker absorbed a trailing empty segment after the last pattern segment (/containers/* matched /containers/a/) but wouldn't spend a pattern segment on one (/*/*/* didn't match /a/b/). Unreachable via NormalizePath, reachable via NormalizePodmanRoutePath, which keeps the slash for the libpod image-SCP route.

This aligns the walker to the regex: a trailing slash is a real empty final segment. The regex is the dialect's definition and the walker only exists to skip regexp on the hot path, so an optimization that answers differently is the bug. Both halves of the old behaviour were policy bugs on the SCP route, in opposite directions: allow POST /libpod/images/scp/* admitted /libpod/images/scp/alpine/ (Podman routes that as image alpine/), and deny POST /libpod/images/scp/*/* missed /libpod/images/scp/tenant/.

No shipped preset changes outcome: all 24 presets under app/configs/ return 403 for every SCP shape tried, with or without the trailing slash, because the decoded view denies first. Differential test now covers both path views, fuzz seeds include the trailing-slash cases, and benchmarks stay at 0 allocs/op.

One pre-existing thing this doesn't touch: a rootless pattern like *libpod/... still matches a rooted path under the walker but not the regex. That lenience is pinned by a test in app/internal/cmd and reachable only via OPTIONS * or an absolute-form request line, so it goes on the roadmap rather than into this PR.

matchGlobSegments is the allocation-free stand-in for the anchored regex a
single-star pattern compiles to, and the two disagreed in both directions on a
path ending in "/". The walker absorbed a trailing empty segment after its last
pattern segment, so "/containers/*" matched "/containers/abc/" where
"^/containers/[^/]*$" does not, and it refused to spend a pattern segment on
that empty segment, so "/*/*/*" did not match "/a/b/" where the regex does.

Align the walker to the regex: a trailing slash is a real, empty final segment,
and a pattern matches only a path carrying exactly as many "/"-separated
segments as the pattern spends. The regex is the dialect's definition and the
walker is only an optimization, so an optimization that answers differently is
the bug by construction.

NormalizePath's path.Clean strips a trailing slash, so neither half is
reachable on an ordinary Docker route. NormalizePodmanRoutePath deliberately
keeps the slash gorilla/mux routes on, so both were reachable on the libpod
image-SCP route view, and both were policy bugs there. Absorbing the slash let
"allow POST /libpod/images/scp/*" admit "/libpod/images/scp/alpine/", which
Podman routes as an SCP of the image "alpine/" rather than of "alpine".
Refusing to spend a segment let "deny POST /libpod/images/scp/*/*" miss
"/libpod/images/scp/tenant/", so the "allow POST /libpod/images/scp/**" below
it fired.

Two alternatives were rejected. Aligning the regex to the walker, so a trailing
slash is ignored, contradicts why NormalizePodmanRoutePath exists: that slash
is what separates the SCP route from the push, tag and untag routes Podman
registers earlier. Normalizing the slash away for matching while preserving it
on the forwarded path is the same defect wearing a different hat, because
"/libpod/images/scp/victim/push/" would then borrow a ".../push" allow and
still be routed as an SCP.

No shipped preset changes. All 24 presets in app/configs deny every
POST /libpod/images/scp/... shape at the decoded view, which is evaluated first
and is untouched here, so the route view is never consulted. Nothing legitimate
narrows either: an image reference cannot end in "/", and the route view is
only computed when the escaped path differs from the cleaned one.

Widen the matcher differential corpus to build from both production path views
instead of NormalizePath alone, which takes it to 993 paths; measured against
the old walker, 512 of its verdicts over the segment-glob patterns disagreed
with the regex, and none do now. Add table-driven cases for both shapes and for
the libpod SCP route through the production evaluator, carry the
walker-versus-regex invariant into FuzzPathMatch on both views, and seed both
fuzz corpora with trailing-slash inputs.
@scttbnsn

scttbnsn commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

Deployment failed for project sockguard-website with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/codeswhat?upgradeToPro=build-rate-limit

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 8bdb2f39-de55-498f-9272-8af0643181c2

📥 Commits

Reviewing files that changed from the base of the PR and between 9d92e84 and 6470c3e.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !CHANGELOG.md
📒 Files selected for processing (5)
  • app/internal/filter/fuzz_test.go
  • app/internal/filter/rules.go
  • app/internal/filter/rules_matcher_differential_test.go
  • docs/content/docs/configuration.mdx
  • docs/content/docs/podman.mdx

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.

The trailing-slash note claimed a two-segment rule covers
POST /libpod/images/scp/alpine/ on its own. evaluateRequestPolicy checks
the cleaned view first and returns on a non-allow, and the cleaned path
has one segment after scp, so a two-segment rule alone denies it. Spell
out that both views have to allow, and keep the /** recommendation.
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

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.

@biggest-littlest biggest-littlest left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Walker now agrees with the regex on trailing slashes; presets unaffected, Codex's one doc finding fixed.

@ALARGECOMPANY ALARGECOMPANY left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Walker now agrees with the regex on trailing slashes; presets unaffected, Codex's one doc finding fixed.

@scttbnsn
scttbnsn merged commit 0c52f55 into dev/v2.1 Sep 4, 2026
62 of 64 checks passed
@scttbnsn
scttbnsn deleted the fix/glob-trailing-slash branch September 4, 2026 13:07
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.

3 participants