Skip to content

feat!: embedded gateway hooks + configurable edge routes#67

Open
polaz wants to merge 11 commits into
mainfrom
feat/#66
Open

feat!: embedded gateway hooks + configurable edge routes#67
polaz wants to merge 11 commits into
mainfrom
feat/#66

Conversation

@polaz

@polaz polaz commented Jun 30, 2026

Copy link
Copy Markdown
Member

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 axum in
its own Cargo.toml
(cargo tree -i axum shows axum only under
structured-proxy). Also makes the previously hardcoded edge routes
configurable. 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 on
    every proxied request and backs /verify (path via with_verify_path).
    Contract uses only http / bytes (zero new deps); Allow injects headers
    (client-forged copies stripped), Deny short-circuits, Redirect 302 inline
    / 401+Location on /verify.
  • OidcBackend — stateless discovery / JWKS / userinfo; every path is
    consumer-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) and metrics (path) are now
configurable and disablable via YAML; defaults unchanged.

Fixes (in scope)

  • RedisStore intra-doc link no longer warns in no-redis builds
    (cfg_attr-gated); docs.rs builds with the redis feature so the type is
    documented.

Breaking change

feat!ProxyConfig gains health and metrics fields. Code that builds
ProxyConfig with a struct literal must add them. YAML configs are
unaffected
(serde defaults). Title carries ! so squash-merge yields a MAJOR
bump.

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 warnings clean (default and redis).
  • cargo doc -D warnings clean (default and redis); doc-tests pass;
    aws_lc_rs backend builds.
  • Standalone binary behavior unchanged.

Follow-ups (separate issues)

  • bff feature: stateful BFF + OIDC authorization server, default-off, composes
    via with_extra_routes.
  • Proxy-as-ext-authz-gRPC-server (feature-gated).
  • Optional single-port co-served tonic gRPC.

Closes #66

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a9cc7708-1fd8-4190-bc03-b062d3c4b1e7

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc0439 and 62bde6c.

📒 Files selected for processing (3)
  • src/lib.rs
  • src/transcode/mod.rs
  • tests/hooks.rs
📝 Walkthrough

Walkthrough

Adds embedded hooks, configurable health and metrics endpoints, router collision checks, and supporting docs, dependencies, and tests.

Changes

Embedded Tier-2 hooks and configuration

Layer / File(s) Summary
Docs and dependency updates
README.md, Cargo.toml, src/shield/store.rs
Updates README guidance and examples, pins direct HTTP dependencies, adds test dependencies, configures docs.rs redis docs, and adjusts feature-gated store docs.
Hook traits and data contracts
src/hooks.rs
Defines RequestParts, Decision, AuthDecider, MetadataDocument, OidcBackend, RouteRequest, RouteResponse, ExtraRouteHandler, and ExtraRoute as public stateless hook contracts.
Axum glue for hooks
src/embed.rs, src/embed/tests.rs
Implements auth gating, verify handling, OIDC routes, extra routes, and helper functions; adds unit tests for token parsing, forwarded headers, gate/verify behavior, OIDC routing, and extra-route body limits.
ProxyServer wiring and route selection
src/lib.rs, src/oidc/mod.rs, src/transcode/mod.rs, tests/hooks.rs
Adds hook fields and builder methods, resolves verify and builtin paths, conditionally mounts auth/health/metrics/OIDC/extra routes, and adds end-to-end coverage for the public API.
Health and metrics config
src/config.rs, tests/embedded.rs
Adds HealthConfig and MetricsConfig to ProxyConfig, validates duplicate enabled paths, and updates the embedded config construction test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title is concise and accurately summarizes the embedded gateway hooks and configurable edge routes.
Description check ✅ Passed The description is detailed and clearly matches the embedding, route, and docs changes in the PR.
Linked Issues check ✅ Passed The PR delivers the axum-free hooks and configurable edge routes needed for the stateless embedded gateway.
Out of Scope Changes check ✅ Passed The changes stay within the embedding, routing, docs, and test scope described by the issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#66

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

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 win

Keep configurable probe and verify paths exempt from maintenance.

maintenance_exempt is still seeded from the static defaults (/health/**, /metrics, /auth/verify). After this change, relocating health.*, metrics.path, or the decider’s verify path means maintenance mode will return 503 for 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

📥 Commits

Reviewing files that changed from the base of the PR and between b33c206 and 57d0ac1.

📒 Files selected for processing (10)
  • Cargo.toml
  • README.md
  • src/config.rs
  • src/embed.rs
  • src/embed/tests.rs
  • src/hooks.rs
  • src/lib.rs
  • src/shield/store.rs
  • tests/embedded.rs
  • tests/hooks.rs

Comment thread README.md
Comment thread src/embed.rs Outdated
Comment thread src/embed.rs Outdated
Comment thread src/embed.rs
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds embedded gateway hooks and configurable edge routes. The main changes are:

  • Axum-free hook traits for auth decisions, OIDC responses, and extra routes.
  • Configurable and disableable health and metrics endpoints.
  • Route-collision checks before router construction.
  • Maintenance exemptions for relocated operational endpoints.
  • Tests for hook behavior, route collisions, and body handling.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

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

Comment thread src/embed.rs Outdated
Comment thread src/config.rs
Comment thread src/lib.rs

@coderabbitai coderabbitai 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.

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 win

Apply 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, so auth.forward_auth.path colliding with health.* or metrics.path will 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

📥 Commits

Reviewing files that changed from the base of the PR and between 57d0ac1 and 1665e6f.

📒 Files selected for processing (6)
  • README.md
  • src/config.rs
  • src/embed.rs
  • src/embed/tests.rs
  • src/lib.rs
  • tests/hooks.rs

Comment thread src/embed.rs Outdated
Comment thread src/lib.rs
polaz added 5 commits June 30, 2026 23:25
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

@coderabbitai coderabbitai 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b40d9d4 and a7270fe.

📒 Files selected for processing (10)
  • Cargo.toml
  • README.md
  • src/config.rs
  • src/embed.rs
  • src/embed/tests.rs
  • src/hooks.rs
  • src/lib.rs
  • src/shield/store.rs
  • tests/embedded.rs
  • tests/hooks.rs

Comment thread src/config.rs
Comment thread src/hooks.rs Outdated
Comment thread src/lib.rs Outdated
Comment thread src/lib.rs Outdated
…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)

@coderabbitai coderabbitai 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.

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 win

Validate verify_path before route registration. with_verify_path() and the auth.forward_auth.path fallback can both supply a path without a leading /; when auth/forward-auth is enabled, that reaches router.route(...) and can panic instead of returning Err.

🤖 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 lift

Include all pre-mounted routes in the verify collision check.
builtin_get_paths() only covers health, metrics, and OpenAPI, but oidc_routes, embed::extra_routes_router(...), and transcode_routes are merged before verify is 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 win

Validate OpenAPI paths with the other built-in edge paths. openapi.path and openapi.docs_path are 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

📥 Commits

Reviewing files that changed from the base of the PR and between a7270fe and 374cab6.

📒 Files selected for processing (4)
  • src/config.rs
  • src/hooks.rs
  • src/lib.rs
  • tests/hooks.rs

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

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

Comment thread src/lib.rs Outdated
- 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
@polaz

polaz commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ 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.

@polaz

polaz commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ 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.

@coderabbitai coderabbitai 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.

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 win

Apply 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

📥 Commits

Reviewing files that changed from the base of the PR and between a7270fe and ee8f57e.

📒 Files selected for processing (6)
  • src/config.rs
  • src/hooks.rs
  • src/lib.rs
  • src/oidc/mod.rs
  • src/transcode/mod.rs
  • tests/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.

@coderabbitai coderabbitai 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ee8f57e and 7d8438b.

📒 Files selected for processing (2)
  • src/lib.rs
  • tests/hooks.rs

Comment thread src/lib.rs Outdated
Comment thread src/lib.rs Outdated
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.
@polaz

polaz commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ 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.

@coderabbitai coderabbitai 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.

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 win

Validate every mounted path before router construction.

reserved_get_paths() includes OIDC and extra-route paths, and those are later handed to Router::route(), which panics on paths that don’t start with /. The current guard only checks verify_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

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8438b and 6cc0439.

📒 Files selected for processing (2)
  • src/lib.rs
  • tests/hooks.rs

Comment thread tests/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.
Comment thread src/lib.rs
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant