Skip to content

F2.C — Auth middleware: Bearer extraction, tier + scope check, header sanitization #29

@HXYerror

Description

@HXYerror

Part of #23. Depends on F2.A, F2.B.

Background

src/server.ts:17-31 mounts proxy routes with no auth. The upstream Copilot bearer is loaded by src/lib/api-config.ts and injected at the service layer.

Goal

Hono middleware that validates Authorization: Bearer sk-cap-…, attaches c.set('key', record), enforces tier and per-key model scope, and guarantees the client's bearer is never proxied upstream.

Tasks

  • src/middleware/auth.ts:
    • Extract Authorization: Bearer … header
    • Reject with 401 + actionable error if header value does NOT start with sk-cap- (per security S10): {"error":"this proxy does not forward your GitHub token; use sk-cap-* key issued by this server"}. Audit-log the rejection (without the token value).
    • sha256 hash the value, lookup via findKeyByHash
    • If not found OR revoked_at IS NOT NULL → 401 invalid_api_key
    • Set c.set('key', record) for downstream
  • Header strip: ensure no client-supplied Authorization, X-Api-Key, Cookie ever reaches fetch() to GitHub. Audit every fetch(...) call site (src/services/copilot/*.ts) and add a unit test asserting no sk-cap- substring in upstream headers (per backend review Streaming reasoning events translation #10).
  • Per-route scope check: chat-completions and messages handlers verify resolved model is in key.allowed_models (or ["*"]). Scope is on the user-facing alias name (per backend Model-to-endpoint routing: chat-completions vs responses #14, F1.C task).
  • Tier check: admin endpoints under /admin/* require tier === 'admin'; client tier → 403.
  • X-Capi-Debug policy (per backend review Emit Anthropic thinking blocks from reasoning channel #18): if header present from a tier=client key, strip silently and log a warning (don't break clients that probe). Only honor on admin tier.
  • --no-auth short-circuit: synthesize {tier:'admin', allowed_models:['*'], id:'__noauth__'} (sentinel id per backend Anthropic /v1/messages -> Responses API adapter (Claude Code on Codex) #17 — for telemetry grouping); log a yellow warning at startup once.
  • Per-key rate-limit bucket (per backend B4): introduce Map<keyId, {lastTs, windowSec}> in middleware; do NOT mutate state.lastRequestTimestamp. Precedence: per-key override > global --rate-limit > none. Update src/lib/rate-limit.ts to read the per-key bucket.
  • Migration runner gate (per security S13): if migrations not yet complete → return 503 from /admin/* and /v1/*.
  • Tests: 401 missing/revoked/wrong-prefix, 403 tier mismatch, scope rejection, no Bearer leak to upstream, per-key rate limit independence, --no-auth short-circuit

Acceptance criteria

  • Unit test asserts upstream fetch headers contain NO sk-cap- substring across all proxy endpoints
  • Two clients with different keys cannot influence each other's rate-limit window
  • tier=client X-Capi-Debug is silently stripped, never honored
  • 401/403 responses include a clear actionable error message

File pointers

  • New: src/middleware/auth.ts, tests/auth.test.ts
  • Touch: src/server.ts, src/routes/chat-completions/handler.ts, src/routes/messages/handler.ts, src/lib/rate-limit.ts

Dependencies

Depends on F2.A, F2.B. Blocks F1.C, F2.E, F3.B, F4.A, F4.B.

Metadata

Metadata

Assignees

No one assigned

    Labels

    authAuthentication / authorizationsecuritySecurity-sensitive

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions