Skip to content

fix(gateway): gate /metrics endpoint behind optional auth and rate limiting - #6756

Merged
bug-ops merged 1 commit into
mainfrom
fix/6550-gateway-oauth-subagent-hardening
Sep 5, 2026
Merged

fix(gateway): gate /metrics endpoint behind optional auth and rate limiting#6756
bug-ops merged 1 commit into
mainfrom
fix/6550-gateway-oauth-subagent-hardening

Conversation

@bug-ops

@bug-ops bug-ops commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #6550

Batch of three low-severity hardening observations from a prior security audit (CI-1429). Re-verified against current code before implementing — two of the three findings had already been fixed upstream since the issue was filed and needed no changes here:

  • F1 (fixed by this PR): /metrics bypassed both the gateway's bearer-token auth and rate limiting. GatewayServer::serve merged an unlayered metrics sub-router onto the router after build_router had already applied auth_middleware/rate_limit_middleware to the protected sub-router — axum::Router::merge does not propagate a layer applied to the outer router onto a router merged in afterward, so no auth mode existed for /metrics at all.
  • F2 (already fixed upstream): MCP OAuth vault-key collision between server IDs differing only by separator. Config::validate_mcp_servers already rejects this at config load time. This PR only extracts the duplicated key-derivation logic (loader.rs and bootstrap/oauth.rs) into a single zeph_config::oauth_vault_key helper and adds collision-path test coverage that was missing.
  • F3 (already fixed upstream): GrantKind::Tool in zeph-subagent is enforced at dispatch via closed issue research(subagent): Tool-kind capability grants are TTL-modeled but never enforced before dispatch #6567 (check_tool_grant / ToolGrantCheck). No changes needed; not touched by this PR.

Changes

  • New opt-in [metrics] require_auth (default false, no behavior change for existing deployments). When true, /metrics requires the same Authorization: Bearer <token> header as /webhook, and gets its own independent per-IP rate-limit counter with the same limit and middleware ordering (rate limit outside auth, so a failed-auth attempt still counts toward the limit) — closing a brute-force gap on the gateway token that an earlier revision of this fix left open (auth-only, no rate limiter).
  • Extracted attach_metrics_route in crates/zeph-gateway/src/server.rs so both serve() and its tests exercise the identical code path — a regression that reintroduces the original unlayered-merge bug now fails the tests instead of only an audit.
  • Threaded require_auth through MetricsConfigGatewayServer::with_metrics_registrygateway_spawn.rsrunner.rs; added to config/default.toml, the --init wizard's Prometheus step, book/src/advanced/gateway.md, and specs/019-gateway/spec.md's Key Invariants.
  • Extracted zeph_config::oauth_vault_key shared helper (F2 residual), used by both Config::validate_mcp_servers and VaultCredentialStore::new; added collision-rejection and negative-case tests.

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins (15406 passed, 0 failed)
  • Rustdoc gate (RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace ...)
  • cargo test --doc -p zeph-gateway --features prometheus
  • gitleaks protect --staged
  • New unit tests: require_auth = true rejects an unauthenticated request (401) and accepts a correctly authenticated one (200); a third failed-auth request against /metrics is rate-limited (429); require_auth = false regression guard confirms the historical unauthenticated/unthrottled default is unchanged.
  • Live-testing playbook (.local/testing/playbooks/gateway.md Scenario 12, 12a-12e) and coverage-status row added for a follow-up live session.
  • Live end-to-end verification (real curl against a running gateway) — pending, tracked in the playbook.

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes config Configuration file changes bug Something isn't working size/L Large PR (201-500 lines) labels Sep 5, 2026
@bug-ops
bug-ops force-pushed the fix/6550-gateway-oauth-subagent-hardening branch from ed2d6d0 to d039210 Compare September 5, 2026 19:40
@bug-ops
bug-ops enabled auto-merge (squash) September 5, 2026 19:40
…miting

Add opt-in `[metrics] require_auth` (default false, preserving existing
behavior) so operators exposing the gateway beyond a trusted scrape network
can require the same bearer token and rate limiting that /webhook already
enforces. Previously /metrics was merged into the router after auth/rate-limit
layers were applied to the protected sub-router, and axum's Router::merge does
not propagate an outer layer onto a router merged in afterward, so no auth
mode existed for /metrics at all.

Also extracts the duplicated MCP OAuth vault-key derivation (loader.rs and
bootstrap/oauth.rs) into a single zeph_config::oauth_vault_key helper, with
new collision-validation test coverage.

Two of the three findings originally batched in #6550 (MCP OAuth vault-key
collision validation, GrantKind::Tool dispatch enforcement) were already
fixed upstream since the issue was filed and required no changes here.

Closes #6550
@bug-ops
bug-ops force-pushed the fix/6550-gateway-oauth-subagent-hardening branch from d039210 to b11682f Compare September 5, 2026 19:42
@bug-ops
bug-ops merged commit e8c7e8a into main Sep 5, 2026
58 checks passed
@bug-ops
bug-ops deleted the fix/6550-gateway-oauth-subagent-hardening branch September 5, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working config Configuration file changes documentation Improvements or additions to documentation rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Low: three hardening observations in gateway/OAuth/subagent trust boundaries

1 participant