From 03b03cf198051beb97e51fa51ca9775b3884d91f Mon Sep 17 00:00:00 2001 From: "umi-appcoder[bot]" Date: Thu, 23 Jul 2026 17:29:35 +0000 Subject: [PATCH] fix(ingress): route the whole /api surface through the Bearer-token ingress (#428) The enumerated whitelist was last extended at #174, silently 404ing every API added since (hypervisor, files, skills, gateway, webhooks, crons, provider-keys, mcp-servers, workspace) for Bearer-token clients on oauth2 deployments. Auth was never enforced by the list: the /bearer-api marker + check_claude_auth gate every endpoint server-side (#161), and the one intentionally unauthenticated /api endpoint (the WhatsApp webhook verify) is provider-signature-gated and already public via its own ingress. Broaden to api/.* and pin the posture with helm unittests so the regex and marker can't regress unnoticed. Co-Authored-By: Claude Fable 5 --- .../templates/ingress-claude-api.yaml | 17 ++++++---- .../tests/bearer_api_ingress_test.yaml | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 charts/workspace/tests/bearer_api_ingress_test.yaml diff --git a/charts/workspace/templates/ingress-claude-api.yaml b/charts/workspace/templates/ingress-claude-api.yaml index 8d80eb95..7c2c46df 100644 --- a/charts/workspace/templates/ingress-claude-api.yaml +++ b/charts/workspace/templates/ingress-claude-api.yaml @@ -54,12 +54,17 @@ spec: http: paths: # The capture group is re-applied by rewrite-target (/$1), so the path - # reaches BrowserHandler unchanged. Covers the task API, memory store, - # the metrics/health probes the mobile client polls, and the Applications - # surface (list + reverse proxy + the WebView session bootstrap under - # api/claude/) — the app proxy accepts a Bearer token or the scoped - # kc_app_session cookie it mints (see server.py check_app_proxy_auth). - - path: /(api/claude/.*|api/memory.*|api/apps|api/app-proxy/.*|api/desktop.*|api/terminal-proxy.*|api/missioncontrol/.*|metrics|health) + # reaches BrowserHandler unchanged. The whole API surface routes here + # deliberately (issue #428): auth is enforced server-side, per endpoint — + # the /bearer-api/ marker makes check_claude_auth demand the Bearer token + # and distrust identity headers (#161), so an enumerated whitelist added + # no security boundary while silently 404ing every API added after #174 + # for the mobile app (hypervisor, files, skills, gateway, …). The only + # /api endpoint that skips check_claude_auth is the WhatsApp webhook + # verify, which is provider-signature-gated and already public via its + # own ingress. /metrics is token-checked; /health is a plain liveness + # probe and intentionally open. + - path: /(api/.*|metrics|health) pathType: ImplementationSpecific backend: service: diff --git a/charts/workspace/tests/bearer_api_ingress_test.yaml b/charts/workspace/tests/bearer_api_ingress_test.yaml new file mode 100644 index 00000000..26970d95 --- /dev/null +++ b/charts/workspace/tests/bearer_api_ingress_test.yaml @@ -0,0 +1,32 @@ +suite: Bearer-token API ingress path coverage (issue #428) +templates: + - templates/ingress-claude-api.yaml +values: + - test-values.yaml +set: + ingress.auth.type: oauth2 + oauth2.cookieSecret: dGVzdGNvb2tpZXNlY3JldDEyMzQ1Njc4OTAxMjM0NTY= + oauth2.clientId: test-client + oauth2.clientSecret: test-secret +tests: + - it: routes the whole /api surface so new endpoints can never silently miss the mobile path + asserts: + - equal: + path: spec.rules[0].http.paths[0].path + value: /(api/.*|metrics|health) + - equal: + path: spec.rules[0].http.paths[0].backend.service.port.number + value: 6080 + + - it: keeps the /bearer-api marker rewrite that pins server-side Bearer-only auth (#161) + asserts: + - equal: + path: metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] + value: /bearer-api/$1 + + - it: is absent outside oauth2 auth mode (basic ingress catch-all covers the API) + set: + ingress.auth.type: basic + asserts: + - hasDocuments: + count: 0