You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
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.
Part of #23. Depends on F2.A, F2.B.
Background
src/server.ts:17-31mounts proxy routes with no auth. The upstream Copilot bearer is loaded bysrc/lib/api-config.tsand injected at the service layer.Goal
Hono middleware that validates
Authorization: Bearer sk-cap-…, attachesc.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:Authorization: Bearer …headersk-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).findKeyByHashrevoked_at IS NOT NULL→ 401invalid_api_keyc.set('key', record)for downstreamAuthorization,X-Api-Key,Cookieever reachesfetch()to GitHub. Audit everyfetch(...)call site (src/services/copilot/*.ts) and add a unit test asserting nosk-cap-substring in upstream headers (per backend review Streaming reasoning events translation #10).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)./admin/*requiretier === 'admin'; client tier → 403.X-Capi-Debugpolicy (per backend review Emit Anthropic thinking blocks from reasoning channel #18): if header present from atier=clientkey, strip silently and log a warning (don't break clients that probe). Only honor on admin tier.--no-authshort-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.Map<keyId, {lastTs, windowSec}>in middleware; do NOT mutatestate.lastRequestTimestamp. Precedence: per-key override > global--rate-limit> none. Updatesrc/lib/rate-limit.tsto read the per-key bucket./admin/*and/v1/*.--no-authshort-circuitAcceptance criteria
fetchheaders contain NOsk-cap-substring across all proxy endpointstier=clientX-Capi-Debugis silently stripped, never honoredFile pointers
src/middleware/auth.ts,tests/auth.test.tssrc/server.ts,src/routes/chat-completions/handler.ts,src/routes/messages/handler.ts,src/lib/rate-limit.tsDependencies
Depends on F2.A, F2.B. Blocks F1.C, F2.E, F3.B, F4.A, F4.B.