Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds embedded hook traits and axum glue, config-driven health and metrics endpoints, router collision checks, and supporting docs, dependencies, and tests. ChangesEmbedded Tier-2 hooks and configuration
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib.rs (1)
252-324: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winKeep configurable probe and verify paths exempt from maintenance.
maintenance_exemptis still seeded from the static defaults (/health/**,/metrics,/auth/verify). After this change, relocatinghealth.*,metrics.path, or the decider’s verify path means maintenance mode will return503for those endpoints, which breaks probe traffic and custom forward-auth even though the default paths were intentionally exempt before. Please derive the exemption list from the resolved configured paths when building the router state, and add a maintenance-mode regression test for the relocated paths.Also applies to: 380-399
🤖 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 `@src/lib.rs` around lines 252 - 324, The maintenance exemption list is still based on the static defaults, so custom health, metrics, and verify endpoints built in the router can be blocked during maintenance. Update the router-state setup around the health/metrics routes and the decider verify path so maintenance_exempt is derived from the resolved configured paths (health.path, health.live_path, health.ready_path, health.startup_path, metrics.path, and the configured verify path) instead of hardcoded defaults. Add a regression test covering relocated probe and verify paths to confirm they stay reachable in maintenance mode.
🤖 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 `@README.md`:
- Around line 201-243: The embedding example currently implies only http, bytes,
and serde_json are needed, but the hook implementation also uses async-trait via
#[async_trait::async_trait]. Update the README section around the embedding
hooks and the AuthDecider example to explicitly mention async-trait as a
required dependency for embedders, or otherwise note that the macro crate must
be added alongside the existing foundational types. Keep the guidance aligned
with the hook traits and ProxyServer::from_config example so readers can locate
the dependency requirement quickly.
In `@src/embed.rs`:
- Around line 253-259: The bearer_token helper only accepts "Bearer " and
"bearer ", so it still rejects other casing like "BEARER token". Update
bearer_token to match the authorization scheme case-insensitively before
trimming the token, while keeping the existing empty-token check and returning
the token from the same helper.
- Around line 153-161: The UserInfo 401 response handling in this branch
currently returns only the JSON body, so update the `backend.userinfo` match in
`src/embed.rs` to add a `WWW-Authenticate` header on every unauthorized
response. Use a plain `Bearer` challenge when `bearer_token(&headers)` yields no
credential, and use `Bearer error="invalid_token"` when
`backend.userinfo(&token)` returns `None`, while keeping the existing
`deny_response` path and JSON error payloads.
- Around line 191-193: The extra-route body buffering in handle() is currently
swallowing failures with unwrap_or_default(), which can pass an empty payload to
the handler. Update the body read path around axum::body::to_bytes in
src/embed.rs to return an error response immediately when buffering fails or
exceeds MAX_EXTRA_ROUTE_BODY, and only continue to the route handler when the
body is successfully read.
---
Outside diff comments:
In `@src/lib.rs`:
- Around line 252-324: The maintenance exemption list is still based on the
static defaults, so custom health, metrics, and verify endpoints built in the
router can be blocked during maintenance. Update the router-state setup around
the health/metrics routes and the decider verify path so maintenance_exempt is
derived from the resolved configured paths (health.path, health.live_path,
health.ready_path, health.startup_path, metrics.path, and the configured verify
path) instead of hardcoded defaults. Add a regression test covering relocated
probe and verify paths to confirm they stay reachable in maintenance mode.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 47f72a28-572b-4e0e-954e-00cfcb5148f5
📒 Files selected for processing (10)
Cargo.tomlREADME.mdsrc/config.rssrc/embed.rssrc/embed/tests.rssrc/hooks.rssrc/lib.rssrc/shield/store.rstests/embedded.rstests/hooks.rs
|
| Filename | Overview |
|---|---|
| src/lib.rs | Adds hook wiring, route reservation checks, configurable edge route mounting, verify route selection, and maintenance exemptions. |
| src/embed.rs | Adds the axum bridge for embedded auth, OIDC, and extra-route handlers. |
| src/config.rs | Adds health and metrics config with defaults and validation for built-in edge paths. |
| src/transcode/mod.rs | Adds route path enumeration for collision checks. |
| src/oidc/mod.rs | Adds OIDC path enumeration for collision checks. |
| src/hooks.rs | Adds public framework-agnostic hook interfaces for embedders. |
Reviews (9): Last reviewed commit: "fix: key route-collision detection on (m..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib.rs (1)
441-458: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winApply the verify-path collision guard to config-driven forward-auth too.
This precheck only runs in the injected-decider branch. A deployment that uses plain JWT forward-auth still merges
forward_auth.routes()without it, soauth.forward_auth.pathcolliding withhealth.*ormetrics.pathwill still hit Axum’s duplicate-route panic instead of returning the clean error this PR adds.🛠️ Proposed fix
let forward_auth = auth.as_ref().and_then(|built| { auth::forward::ForwardAuth::build(self.config.auth.as_ref()?, built.clone()) }); + + if (self.auth_decider.is_some() || forward_auth.is_some()) + && self.builtin_get_paths().iter().any(|p| p == &verify_path) + { + anyhow::bail!( + "verify path {verify_path:?} collides with a health/metrics endpoint path" + ); + } // Auth runs inside Shield (added first = inner): rate limiting sheds // load before any signature verification work. if let Some(auth) = auth { router = router.layer(axum::middleware::from_fn_with_state(auth, auth::middleware)); @@ // Forward-auth `/verify` endpoint. An injected AuthDecider owns it when // present (in-process PDP); otherwise the config-driven JWT ForwardAuth // backs it. Mounted after the auth layer so it is not itself JWT-gated. if let Some(decider) = &self.auth_decider { - // Guard against the verify path colliding with a built-in GET route - // (axum panics on duplicate path registration); fail with a clear - // error instead. `verify_path` was resolved once above. - if self.builtin_get_paths().iter().any(|p| p == &verify_path) { - anyhow::bail!( - "verify path {verify_path:?} collides with a health/metrics endpoint path" - ); - } let decider = decider.clone(); router = router.route( &verify_path, axum::routing::any(move |req: axum::extract::Request| { let decider = decider.clone();🤖 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 `@src/lib.rs` around lines 441 - 458, The verify-path collision check is only applied in the injected-decider branch, so plain JWT forward-auth can still merge `forward_auth.routes()` with a conflicting `auth.forward_auth.path`. Update the routing setup in `src/lib.rs` so the same `builtin_get_paths()` vs `verify_path` precheck runs before both the decider route registration and the `forward_auth.routes()` merge, returning the same clear `anyhow::bail!` error instead of letting Axum panic.
🤖 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 `@src/embed.rs`:
- Around line 153-170: Short-circuit missing Bearer credentials in the
`/userinfo` flow before calling `backend.userinfo`. In the `bearer_token`
handling inside `src/embed.rs`, avoid passing `token.as_deref().unwrap_or("")`
to `userinfo`; instead return the same unauthorized challenge path when
`bearer_token(&headers)` yields `None`, and only invoke `backend.userinfo(...)`
when a real token is present. Keep the existing `invalid_token` vs plain
`Bearer` challenge behavior in the match around `userinfo`.
---
Outside diff comments:
In `@src/lib.rs`:
- Around line 441-458: The verify-path collision check is only applied in the
injected-decider branch, so plain JWT forward-auth can still merge
`forward_auth.routes()` with a conflicting `auth.forward_auth.path`. Update the
routing setup in `src/lib.rs` so the same `builtin_get_paths()` vs `verify_path`
precheck runs before both the decider route registration and the
`forward_auth.routes()` merge, returning the same clear `anyhow::bail!` error
instead of letting Axum panic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 90cbdb7f-1485-4c2a-8b8b-1b3e063eed24
📒 Files selected for processing (6)
README.mdsrc/config.rssrc/embed.rssrc/embed/tests.rssrc/lib.rstests/hooks.rs
Add axum-free Tier-2 embedding hooks so a service gets the full stateless REST/OIDC/forward-auth edge by adding the crate, with no axum in its own Cargo.toml: - AuthDecider: in-process forward-auth/PDP gate, runs inline on proxied routes and backs /verify (path set via with_verify_path) - OidcBackend: stateless discovery/JWKS/userinfo, paths consumer-supplied - with_extra_routes: framework-agnostic route adapter (request/response parts) - Make health and metrics endpoint paths configurable and disablable - Fix RedisStore intra-doc link in no-redis builds; docs.rs builds with the redis feature so the type is documented Hook contracts use only http/bytes/serde_json, so AuthDecider adds zero new dependencies. Standalone binary behavior is unchanged. BREAKING CHANGE: ProxyConfig gains `health` and `metrics` fields; code that builds ProxyConfig with a struct literal must add them. YAML configs are unaffected (serde defaults fill them in). Closes #66
…scheme - Reject oversized/unreadable extra-route bodies with 413 instead of passing an empty payload to the handler (a body-parsing handler would misread it) - Add RFC 6750 WWW-Authenticate Bearer challenge to userinfo 401 responses (plain Bearer when no credentials, error="invalid_token" when rejected) - Match the Authorization Bearer scheme case-insensitively
…sion-free - Reject duplicate built-in endpoint paths at config load (e.g. health.path equal to the default live_path) instead of panicking on duplicate route registration - Derive the maintenance exemption list from the resolved configured health, metrics, and verify paths so relocating them does not 503 probe/forward-auth traffic that was exempt at the default paths - Fail with a clear error when the verify path collides with a built-in GET endpoint path
The hook traits are #[async_trait], so embedders need async-trait alongside http/bytes/serde_json. None is an HTTP framework.
…llision - /userinfo now returns the 401 Bearer challenge without invoking the OIDC backend when no credentials are present (never call it with an empty token) - Apply the verify-path / built-in-GET collision guard before both the injected decider route and the config-driven JWT forward-auth merge, so plain forward-auth also returns a clean error instead of an axum duplicate-route panic
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@src/config.rs`:
- Around line 646-662: validate_edge_paths() only checks for duplicate built-in
endpoint paths, but Router::route expects valid route shapes. Update
validate_edge_paths in src/config.rs to also reject invalid health and metrics
paths before they reach Router::route, using the existing path fields on
self.health and self.metrics so bad config fails fast at validation time instead
of during startup routing.
In `@src/hooks.rs`:
- Around line 116-119: The `Hooks::userinfo` documentation still says `bearer`
may be an empty string, but `src/embed.rs` now bypasses the hook when
credentials are missing. Update the `userinfo` contract comment in `Hooks` to
state that it is only called with a present bearer token after the `Bearer `
prefix is stripped, and remove the empty-string/missing-credentials wording so
the doc matches the actual `userinfo` call path.
In `@src/lib.rs`:
- Around line 298-306: The middleware layering in the route setup is reversed:
auth_decider_gate is currently applied after auth::authz::middleware, so it
executes before authz instead of after it. Update the layering in src/lib.rs
where transcode_routes is built so the AuthDecider middleware is inserted before
the authz layer, preserving the intended authz -> AuthDecider -> handler order;
use the existing auth_decider_gate and auth::authz::middleware symbols to adjust
the wrapping sequence.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a48eb3b2-a81d-4752-965d-3e02cea645a6
📒 Files selected for processing (10)
Cargo.tomlREADME.mdsrc/config.rssrc/embed.rssrc/embed/tests.rssrc/hooks.rssrc/lib.rssrc/shield/store.rstests/embedded.rstests/hooks.rs
…cider - builtin_get_paths now includes the enabled OpenAPI spec/docs paths, so a verify path colliding with /openapi.json or /docs is rejected with a clear error instead of an axum duplicate-route panic - validate_edge_paths rejects paths without a leading '/' (axum would reject the route shape at construction otherwise) - order the proxied-route gates authz -> AuthDecider -> handler (the decider layer is added inner, authz outer), so ext_authz runs first and the decider sees headers it injected; the previous order was inverted - correct the OidcBackend::userinfo doc: it only ever receives a present, non-empty bearer token (missing credentials are rejected before the call)
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/lib.rs (2)
443-467: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate
verify_pathbefore route registration.with_verify_path()and theauth.forward_auth.pathfallback can both supply a path without a leading/; when auth/forward-auth is enabled, that reachesrouter.route(...)and can panic instead of returningErr.🤖 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 `@src/lib.rs` around lines 443 - 467, Validate verify_path before calling router.route in the routing setup inside src/lib.rs, because with_verify_path() and the auth.forward_auth.path fallback can yield a path without a leading slash and cause a panic. Add normalization/validation right after the verify_path is chosen and before the collision check and route registration, so invalid paths are rejected with an error instead of reaching the AuthDecider/forward-auth verify endpoint mount.
203-222: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftInclude all pre-mounted routes in the verify collision check.
builtin_get_paths()only covers health, metrics, and OpenAPI, butoidc_routes,embed::extra_routes_router(...), andtranscode_routesare merged beforeverifyis registered. A custom/userinfo, extra route, or transcoded GET path can still collide and panic at router construction; guard the full reserved path set instead.🤖 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 `@src/lib.rs` around lines 203 - 222, The verify collision guard is incomplete because builtin_get_paths only returns health, metrics, and OpenAPI paths, while oidc_routes, embed::extra_routes_router(...), and transcode_routes are mounted before verify and can still conflict. Update the reserved-path collection used by the verify registration check to include every pre-mounted GET route from those route builders, and ensure the guard in builtin_get_paths (or its caller) reflects the full set of paths that can be registered before verify.src/config.rs (1)
647-666: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate OpenAPI paths with the other built-in edge paths.
openapi.pathandopenapi.docs_pathare also mounted, so a collision with health/metrics (or between the two OpenAPI routes) can still panic when the router is assembled.Proposed fix
if self.metrics.enabled { check("metrics.path", &self.metrics.path)?; } + if let Some(openapi) = self.openapi.as_ref().filter(|o| o.enabled) { + check("openapi.path", &openapi.path)?; + check("openapi.docs_path", &openapi.docs_path)?; + } Ok(()) }🤖 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 `@src/config.rs` around lines 647 - 666, The built-in path validation in validate_edge_paths currently checks only health and metrics, so openapi.path and openapi.docs_path can still collide with those routes or with each other and later panic during router setup. Extend the existing HashSet-based duplicate/path-prefix checks in validate_edge_paths to include both OpenAPI routes when openapi is enabled, using the same check helper and distinct labels so all built-in edge paths are validated consistently.
🤖 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.
Outside diff comments:
In `@src/config.rs`:
- Around line 647-666: The built-in path validation in validate_edge_paths
currently checks only health and metrics, so openapi.path and openapi.docs_path
can still collide with those routes or with each other and later panic during
router setup. Extend the existing HashSet-based duplicate/path-prefix checks in
validate_edge_paths to include both OpenAPI routes when openapi is enabled,
using the same check helper and distinct labels so all built-in edge paths are
validated consistently.
In `@src/lib.rs`:
- Around line 443-467: Validate verify_path before calling router.route in the
routing setup inside src/lib.rs, because with_verify_path() and the
auth.forward_auth.path fallback can yield a path without a leading slash and
cause a panic. Add normalization/validation right after the verify_path is
chosen and before the collision check and route registration, so invalid paths
are rejected with an error instead of reaching the AuthDecider/forward-auth
verify endpoint mount.
- Around line 203-222: The verify collision guard is incomplete because
builtin_get_paths only returns health, metrics, and OpenAPI paths, while
oidc_routes, embed::extra_routes_router(...), and transcode_routes are mounted
before verify and can still conflict. Update the reserved-path collection used
by the verify registration check to include every pre-mounted GET route from
those route builders, and ensure the guard in builtin_get_paths (or its caller)
reflects the full set of paths that can be registered before verify.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fc5a97b5-f553-42e2-95b8-1e968c27c04f
📒 Files selected for processing (4)
src/config.rssrc/hooks.rssrc/lib.rstests/hooks.rs
|
T-Rex pricing update — T-Rex was free through June 2026. Effective July 1, 2026, T-Rex adds 2 credits on top of the standard 1-credit review (3 total). T-Rex settings |
- reserved_get_paths now collects every route mounted before verify: health, metrics, OpenAPI, the OIDC surface (injected backend or config-driven static discovery), embedder extra routes, and the transcoded REST routes (transcode::route_paths). A verify path colliding with any of them is now a clear error instead of an axum duplicate-route panic - reject a verify path without a leading '/' before route registration - validate_edge_paths also checks OpenAPI spec/docs paths for shape and duplicates
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib.rs (1)
203-241: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winApply the reserved-path check to every mounted edge route. The helper already collects health, metrics, OpenAPI, OIDC, extra-route, and transcode paths, but it is only used for
/verify. Any collision among mounted routes can still reach Axum and abort startup with a duplicate-route panic.🤖 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 `@src/lib.rs` around lines 203 - 241, The reserved-path guard in reserved_get_paths currently covers all mounted edge route categories, but the check is only being applied for /verify, so other mounted routes can still collide and trigger an Axum duplicate-route panic at startup. Update the route registration flow in lib.rs to run this reserved-path validation for every mounted edge route (not just verify), reusing reserved_get_paths along the mounting path so any conflicting path is rejected with the existing clear error before registration.
🤖 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.
Outside diff comments:
In `@src/lib.rs`:
- Around line 203-241: The reserved-path guard in reserved_get_paths currently
covers all mounted edge route categories, but the check is only being applied
for /verify, so other mounted routes can still collide and trigger an Axum
duplicate-route panic at startup. Update the route registration flow in lib.rs
to run this reserved-path validation for every mounted edge route (not just
verify), reusing reserved_get_paths along the mounting path so any conflicting
path is rejected with the existing clear error before registration.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 55df3552-868b-4e16-b326-b6a9498d1787
📒 Files selected for processing (6)
src/config.rssrc/hooks.rssrc/lib.rssrc/oidc/mod.rssrc/transcode/mod.rstests/hooks.rs
Run the reserved-path duplicate scan across the whole mounted edge (built-in routes, OIDC surface, extra routes, transcoded paths, and the verify endpoint) up front, before any router is constructed. Any collision now returns a clear error instead of reaching axum and panicking at .route/.merge during startup.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/lib.rs`:
- Around line 261-277: The mounted verify path and the path used for
validation/exemptions are diverging in the JWT forward-auth flow, so collisions
and maintenance exemptions can apply to the wrong route. Update the logic around
self.resolved_verify_path(), ForwardAuth::build, and forward_auth.routes() so
the same path that is actually mounted is also the one added to mounted and used
for maintenance exemptions; keep the decider-driven route using the override,
but for config-driven forward auth validate/exempt auth.forward_auth.path unless
the override is explicitly threaded through.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 6474cf96-98b6-42f6-b0af-1c166c091965
📒 Files selected for processing (2)
src/lib.rstests/hooks.rs
The with_verify_path override applies only to an injected AuthDecider's verify route; config-driven JWT forward-auth always mounts at auth.forward_auth.path. Introduce mounted_verify_path() that returns the branch-correct path (decider → override/config/default; config forward-auth → forward_auth.path, only when mode is jwt), and use it for both the duplicate-route guard and the maintenance-exempt list. Previously a with_verify_path override hid a config forward_auth.path collision and exempted the wrong path under maintenance.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib.rs (1)
294-306: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate every mounted path before router construction.
reserved_get_paths()includes OIDC and extra-route paths, and those are later handed toRouter::route(), which panics on paths that don’t start with/. The current guard only checksverify_path, so a malformed backend or extra-route path can still turn startup into a panic instead of a build error.Proposed fix
if let Some(vp) = &verify_path { if !vp.starts_with('/') { anyhow::bail!("verify path {vp:?} must start with '/'"); } mounted.push(vp.clone()); } + for path in &mounted { + if !path.starts_with('/') { + anyhow::bail!("route path {path:?} must start with '/'"); + } + } let mut seen = std::collections::HashSet::with_capacity(mounted.len());🤖 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 `@src/lib.rs` around lines 294 - 306, The startup path validation only checks verify_path, but mounted also includes values from reserved_get_paths() that later go into Router::route(). Add a pre-router validation pass over every entry in mounted to ensure each path starts with '/' and return an anyhow::bail! build error if any do not. Keep the existing duplicate-path check, and make the fix in the mounting logic around reserved_get_paths(), mounted, and Router::route() so malformed backend or extra-route paths can’t cause a panic.
🤖 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 `@tests/hooks.rs`:
- Around line 420-433: The YAML snippet in `ProxyConfig::from_yaml_str` is
embedding `pem_path.display()` directly into a double-quoted scalar, which can
break on Windows because backslashes are treated as escapes. Update the test to
pass an escaped/serialized path string when interpolating the
`public_key_pem_file` value so the generated YAML remains valid across
platforms.
---
Outside diff comments:
In `@src/lib.rs`:
- Around line 294-306: The startup path validation only checks verify_path, but
mounted also includes values from reserved_get_paths() that later go into
Router::route(). Add a pre-router validation pass over every entry in mounted to
ensure each path starts with '/' and return an anyhow::bail! build error if any
do not. Keep the existing duplicate-path check, and make the fix in the mounting
logic around reserved_get_paths(), mounted, and Router::route() so malformed
backend or extra-route paths can’t cause a panic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 6368cf48-ba68-4c4b-9d0f-b0b1b0ea912d
📒 Files selected for processing (2)
src/lib.rstests/hooks.rs
reserved_get_paths() includes consumer-supplied OIDC-backend and extra-route paths that are handed to Router::route(), which panics on a path without a leading '/'. Validate the leading '/' for every mounted path (not only the verify path) before building the router, so a malformed backend/extra-route path is a clean build error. Also embed the temp PEM path in test YAML with forward slashes so the config parses on Windows runners.
Same-path routes with different methods (e.g. an embedder registering GET /c and POST /c through the extra-route adapter, which merges them by method) are a legal shape. The collision guard now keys on (method, path): only a repeated (method, path) is a conflict. The verify endpoint answers all methods, so it is tracked as '*' and conflicts with any route on its path. route_paths and reserved_routes now carry the HTTP method for each route.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@src/lib.rs`:
- Around line 229-276: `reserved_routes` is only comparing raw `(method, path)`
strings, so dynamic routes with the same shape can still collide at router build
time. Update the guard in `reserved_routes` (and any helper it uses) to
normalize each route’s shape before deduping/comparing, so equivalent templated
paths like different parameter names are treated as the same conflict. Keep the
existing method/path pairing logic, but base the collision check on normalized
route structure rather than literal path text.
In `@src/transcode/mod.rs`:
- Around line 195-226: `route_paths` is duplicating the route/alias iteration
from `routes`, which can drift and break the pre-mount collision guard. Refactor
the shared unary/alias/streaming enumeration into one internal helper (for
example, a builder or iterator used by `routes` and `route_paths`) and have both
functions consume it so the `(method, path)` list always matches what `routes`
mounts. Use the existing `route_paths`, `routes`, `extract_routes`, and
`extract_streaming_routes` symbols to keep the logic centralized.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5a69e9ce-ccae-4e1e-b369-a7e61405d0ca
📒 Files selected for processing (3)
src/lib.rssrc/transcode/mod.rstests/hooks.rs
| /// Every `(method, path)` route mounted before the verify endpoint, used to | ||
| /// reject a real collision with a clear error instead of an axum | ||
| /// duplicate-route panic. `method` is the uppercase HTTP token; same-path | ||
| /// routes with different methods do NOT collide (the extra-route adapter and | ||
| /// axum merge them), so the key is the pair, not the path alone. | ||
| /// | ||
| /// Must stay exhaustive: health probes, metrics, OpenAPI spec/docs, the OIDC | ||
| /// surface (injected backend or config-driven static discovery), embedder | ||
| /// extra routes, and the transcoded REST routes. All built-in surfaces here | ||
| /// are `GET`. | ||
| fn reserved_routes(&self, pool: &DescriptorPool) -> anyhow::Result<Vec<(String, String)>> { | ||
| let mut routes = Vec::new(); | ||
| let mut get = |path: String| routes.push(("GET".to_string(), path)); | ||
| if self.config.health.enabled { | ||
| get(self.config.health.path.clone()); | ||
| get(self.config.health.live_path.clone()); | ||
| get(self.config.health.ready_path.clone()); | ||
| get(self.config.health.startup_path.clone()); | ||
| } | ||
| if self.config.metrics.enabled { | ||
| get(self.config.metrics.path.clone()); | ||
| } | ||
| if let Some(openapi) = self.config.openapi.as_ref().filter(|o| o.enabled) { | ||
| get(openapi.path.clone()); | ||
| get(openapi.docs_path.clone()); | ||
| } | ||
| // OIDC: an injected backend supersedes config-driven static discovery. | ||
| if let Some(backend) = &self.oidc_backend { | ||
| for doc in backend.metadata_documents() { | ||
| get(doc.path); | ||
| } | ||
| get(backend.jwks().path); | ||
| get(backend.userinfo_path()); | ||
| } else if let Some(cfg) = &self.config.oidc_discovery { | ||
| if let Some(oidc) = oidc::Oidc::build(cfg) | ||
| .map_err(|e| anyhow::anyhow!("invalid oidc_discovery config: {e}"))? | ||
| { | ||
| for path in oidc.paths() { | ||
| get(path); | ||
| } | ||
| } | ||
| } | ||
| for route in &self.extra_routes { | ||
| routes.push((route.method.as_str().to_string(), route.path.clone())); | ||
| } | ||
| routes.extend(transcode::route_paths(pool, &self.config.aliases)); | ||
| Ok(routes) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect relevant source around the reported lines and route-building logic.
git ls-files src/lib.rs Cargo.toml Cargo.lock
echo '--- src/lib.rs outline ---'
ast-grep outline src/lib.rs --view expanded || true
echo '--- src/lib.rs lines 200-360 ---'
sed -n '200,360p' src/lib.rs
echo '--- dependencies mentioning axum/matchit ---'
rg -n '"axum"|matchit' Cargo.toml Cargo.lockRepository: structured-world/structured-proxy
Length of output: 10197
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- Cargo.toml axum/matchit entries ---'
rg -n 'axum|matchit' Cargo.toml
echo '--- Cargo.toml relevant section ---'
sed -n '1,220p' Cargo.tomlRepository: structured-world/structured-proxy
Length of output: 5391
🌐 Web query:
axum matchit route conflict different parameter names same shape duplicate route panic
💡 Result:
In axum, registering routes that have the same "shape" (the same number of segments and placement of parameters) but different parameter names is not inherently allowed if those routes are considered overlapping by the underlying matchit router [1][2]. When the router determines that two routes overlap in an ambiguous way, registering them will cause the application to panic [1][2][3]. The matchit router follows strict conflict rules to ensure that route matching remains unambiguous [4][5]. Specifically: 1. Ambiguity Detection: If two routes have overlapping segments that do not fit into the allowed priority patterns (such as static segments taking precedence over dynamic ones), the router considers the combination ambiguous [4][5]. Attempting to insert such a route into the router results in an error, which axum turns into a panic during the setup phase [1][2][3]. 2. Parameter Names: While the matchit router is primarily concerned with the path structure, having two routes that effectively occupy the same "slot" in the routing tree often triggers a conflict error [6][1]. Even if the parameter names differ (e.g., /:id vs /:user_id), the router views these as having the same structural shape at that position [1][7]. If the routes are registered such that they compete for the same request path, the router may fail to determine a deterministic match, leading to the panic [1][2]. 3. Conflict Exceptions: Static segments have higher priority than dynamic ones (parameters) [4][5][8]. If you have a static path and a dynamic path, they are typically not considered to overlap, and the static one wins [8][3]. However, conflicts between two dynamic paths—even with different parameter names—often cannot be resolved by the router, resulting in the reported panic [1][2]. To avoid this, you must ensure your route paths have distinct shapes that allow the router to unambiguously distinguish between them, or refactor your application to handle parameter variations within the handler logic rather than at the routing layer [7].
Citations:
- 1: Route conflict although they define different segments tokio-rs/axum#2079
- 2: Overlapping routes with differing methods cause a panic tokio-rs/axum#1498
- 3: https://github.com/tokio-rs/axum/blob/60a0d283/axum/src/docs/routing/route.md
- 4: https://docs.rs/matchit/latest/matchit/
- 5: https://github.com/ibraheemdev/matchit
- 6: Overlapping routes with captures having different names panics tokio-rs/axum#2218
- 7: Better support for overlapping parameters tokio-rs/axum#678
- 8: https://docs.rs/axum/latest/axum/routing/struct.Router.html
🌐 Web query:
matchit same shape dynamic routes different parameter names conflict route insertion docs
💡 Result:
In the matchit routing library, route segments are allowed to overlap provided they follow specific precedence rules [1][2]. If routes do not adhere to these rules—or if the combination of segments is considered ambiguous—the router will return an error during insertion [1][3]. Regarding dynamic routes with different parameter names: The router effectively treats dynamic parameters as placeholders [1]. While different parameter names (e.g., /{id} vs /{user_id}) are technically distinct, the library focuses on the structure of the segments (static, parameterized, or catch-all) to resolve conflicts [1][3]. Specifically: - Static segments always take priority over dynamic segments [1][2]. - For parameterized segments, the library prioritizes based on the length of prefixes and suffixes (e.g., /{x}a vs /{y}b, prioritizing the longest suffix) [3][2]. - Only one named parameter is allowed per route segment [3]. Historical issues regarding "insertion order conflicts"—where identical shapes with different parameter names or trailing slashes would fail depending on the order they were inserted—have been addressed in recent updates, such as version 0.9.2 [4][5]. If you are encountering an insertion error with what appears to be valid routing, ensure you are using the latest version of the library, as inconsistencies in how the trie handles prefix/suffix matching and trailing slashes have been actively resolved to prevent order-dependent failures [4][5]. If a route is rejected, it is typically because the structure is inherently ambiguous according to the library's precedence logic, or it violates the constraint that only a single dynamic segment type can exist at a specific position in the trie [1][2]. Catch-all parameters, for instance, have specific constraints and are explicitly documented to conflict with suffixed route parameters due to current implementation details [1][6][2].
Citations:
- 1: https://github.com/ibraheemdev/matchit
- 2: https://docs.rs/matchit/latest/matchit/index.html
- 3: https://github.com/ibraheemdev/matchit/blob/master/README.md
- 4: Unexected (and inconsistent) insertion conflict on prefix+suffix parameter ibraheemdev/matchit#92
- 5: ibraheemdev/matchit@287087c
- 6: https://docs.rs/matchit/latest/matchit/
🌐 Web query:
matchit conflict identical dynamic routes different parameter names issue docs route shape
💡 Result:
In the matchit router, attempting to register dynamic routes that are structurally identical—such as /:name and /:id—will result in an insertion conflict error [1]. The library requires that dynamic segments in the same position within a route path maintain structural compatibility [2][3]. 1. Structural Conflict: Matchit treats routes with different parameter names (e.g., /:id vs /:name) as potentially ambiguous if they occupy the same position in the routing tree [1]. When you define both /:name and /:id, the router detects an insertion conflict because both patterns are essentially identical and compete for the same path segments [1]. 2. Resolution: To resolve this, you must use the same parameter name for routes that share the same structural pattern (e.g., use /:id for both or /:name for both) [1]. This ensures the routing tree remains unambiguous [2][3]. 3. Understanding Conflict Rules: Matchit enforces strict rules to prevent route ambiguity [2][4]. While static and dynamic segments can overlap (with static segments given higher priority), combinations of dynamic parameters must not conflict [2][3]. Specifically, any route segment combination that the router cannot uniquely resolve is considered ambiguous and will cause an insertion error [2][5]. While some historical issues reported panics or confusing errors, these are generally resolved by adhering to consistent parameter naming within identical route structures [1][6]. The library's documentation details specific precedence rules (e.g., static segments > suffixed parameters > standalone parameters > catch-all parameters), which dictate how routes are matched [2][3]. Any structure that violates these defined precedence or creates overlap between incompatible dynamic types is flagged as an error at insertion time [2][5].
Citations:
- 1: Overlapping routes with captures having different names panics tokio-rs/axum#2218
- 2: https://github.com/ibraheemdev/matchit
- 3: https://docs.rs/crate/matchit/latest
- 4: https://github.com/ibraheemdev/matchit/blob/master/README.md
- 5: https://docs.rs/matchit/latest/matchit/index.html
- 6: failed to register Get route for /:path pattern: insertion failed due to conflict with previously registered route: //' ibraheemdev/matchit#33
Normalize the collision key by route shape. reserved_routes only compares literal path strings, so structurally identical dynamic routes like /v1/profiles/{profile_id} and /v1/profiles/{id} can still slip through even though axum/matchit treats them as a conflict and can panic when the router is built. Key this guard by normalized shape, not raw path text.
🤖 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 `@src/lib.rs` around lines 229 - 276, `reserved_routes` is only comparing raw
`(method, path)` strings, so dynamic routes with the same shape can still
collide at router build time. Update the guard in `reserved_routes` (and any
helper it uses) to normalize each route’s shape before deduping/comparing, so
equivalent templated paths like different parameter names are treated as the
same conflict. Keep the existing method/path pairing logic, but base the
collision check on normalized route structure rather than literal path text.
| /// The axum paths [`routes`] would register for this pool and aliases. | ||
| /// | ||
| /// Mirrors the registration in [`routes`] (unary RPCs, their config aliases, and | ||
| /// server-streaming RPCs) without building handlers, so callers can detect route | ||
| /// collisions before mounting additional routes (e.g. a forward-auth endpoint). | ||
| /// | ||
| /// Each entry is `(method, path)` where `method` is the uppercase HTTP token, so | ||
| /// callers can distinguish same-path/different-method routes from real conflicts. | ||
| pub fn route_paths(pool: &DescriptorPool, aliases: &[AliasConfig]) -> Vec<(String, String)> { | ||
| let mut routes = Vec::new(); | ||
| for entry in extract_routes(pool) { | ||
| let method = entry.http_method.as_str().to_string(); | ||
| routes.push((method.clone(), proto_path_to_axum(&entry.http_path))); | ||
| for alias in aliases { | ||
| if let Some(suffix) = entry.http_path.strip_prefix(&alias.to) { | ||
| if alias.from.ends_with("/{path}") { | ||
| let prefix = alias.from.trim_end_matches("/{path}"); | ||
| routes.push((method.clone(), format!("{prefix}{suffix}"))); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for entry in extract_streaming_routes(pool) { | ||
| if matches!(entry.http_method, HttpMethod::Get | HttpMethod::Post) { | ||
| routes.push(( | ||
| entry.http_method.as_str().to_string(), | ||
| proto_path_to_axum(&entry.http_path), | ||
| )); | ||
| } | ||
| } | ||
| routes | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
route_paths duplicates routes()'s entry/alias logic — risk of guard drift.
route_paths re-implements the same unary/alias/streaming-method iteration as routes() purely to enumerate (method, path) pairs. Since src/lib.rs's pre-mount collision guard relies entirely on this list being an accurate mirror of what routes() actually mounts, any future change to one function without the other silently defeats the "no axum panics" guarantee this PR introduces.
Consider extracting a single internal iterator/builder (e.g. fn route_entries(pool, aliases) -> Vec<RouteBinding>) that both routes() (to build handlers) and route_paths() (to enumerate paths) consume, so the two can't diverge.
🤖 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 `@src/transcode/mod.rs` around lines 195 - 226, `route_paths` is duplicating
the route/alias iteration from `routes`, which can drift and break the pre-mount
collision guard. Refactor the shared unary/alias/streaming enumeration into one
internal helper (for example, a builder or iterator used by `routes` and
`route_paths`) and have both functions consume it so the `(method, path)` list
always matches what `routes` mounts. Use the existing `route_paths`, `routes`,
`extract_routes`, and `extract_streaming_routes` symbols to keep the logic
centralized.
Summary
Adds the axum-free Tier-2 embedding hooks so a service gets the full
stateless REST/OIDC/forward-auth edge by adding the crate, with no
axuminits own
Cargo.toml(cargo tree -i axumshows axum only understructured-proxy). Also makes the previously hardcoded edge routesconfigurable. The default build stays a stateless data plane (README Non-goals
hold).
Hooks (
structured_proxy::hooks, all axum-free)AuthDecider— in-process forward-auth / PDP decision. Runs inline onevery proxied request and backs
/verify(path viawith_verify_path).Contract uses only
http/bytes(zero new deps);Allowinjects headers(client-forged copies stripped),
Denyshort-circuits,Redirect302 inline/ 401+
Locationon/verify.OidcBackend— stateless discovery / JWKS / userinfo; every path isconsumer-supplied; supersedes config-driven static discovery.
with_extra_routes— framework-agnostic route adapter (request parts in,response parts out); merges methods on a shared path.
Configurable edge routes
health(path / live / ready / startup) andmetrics(path) are nowconfigurable and disablable via YAML; defaults unchanged.
Fixes (in scope)
RedisStoreintra-doc link no longer warns in no-redisbuilds(
cfg_attr-gated); docs.rs builds with theredisfeature so the type isdocumented.
Breaking change
feat!—ProxyConfiggainshealthandmetricsfields. Code that buildsProxyConfigwith a struct literal must add them. YAML configs areunaffected (serde defaults). Title carries
!so squash-merge yields a MAJORbump.
Testing
cargo nextest run --workspace: 160 passed (+28), incl. negative / edge /security paths (forged-header strip, malformed location, unsupported method
skip, no-forwarding-header fallback, disabled/relocated routes).
cargo clippy --all-targets -- -D warningsclean (default andredis).cargo doc -D warningsclean (default andredis); doc-tests pass;aws_lc_rsbackend builds.Follow-ups (separate issues)
bfffeature: stateful BFF + OIDC authorization server, default-off, composesvia
with_extra_routes.Closes #66