The degradation itself is honest and PASSES — please do not "fix" that half: /ai/models, /conversations, /usage, /chat all 501 with the Cloud/EE remedy sentence (mounted-but-unimplemented, never 404/503); /ai/agents returns the declared envelope with the relocated payload; and /discovery's services.ai message is string-identical to the 501 body. The defect below is only the ordering of the anonymous-deny gate.
Symptom
Unauthenticated callers reach the /ai/** family instead of being denied.
- Observed: unauth
GET /api/v1/ai/models → 501 (leaking the Cloud/EE remedy sentence); unauth GET /api/v1/ai/agents → 200. Reproduced twice. - Expected: both → 401.
- Control (proves anonymous-deny is otherwise live on the same boot): anonymous
GET /api/v1/data/showcase_task → 401, correctly.
Disclosure impact is limited to two static strings, but the ordering contract this item exists to pin is inverted.
Root cause
runtime/src/domains/ai.tshandleAIRequest returns the /ai/agents empty-list courtesy ({ agents: [] }) and capabilityUnavailable(deps, 'ai') at the top of the handler (around ai.ts:68-79), while the shouldDenyAnonymous gate lives inside the per-route loop further down (ai.ts:128) — reachable only when the AI service is serveable. So on every open-edition deployment the whole /ai/** family answers unauthenticated callers before the anonymous gate is ever consulted. Still present on origin/main (the courtesy + capabilityUnavailable return precedes the shouldDenyAnonymous check).
Reproduction
On a stock boot, with no Authorization header:
curl http://<host>/api/v1/ai/models # expected 401, actual 501
curl http://<host>/api/v1/ai/agents # expected 401, actual 200
curl http://<host>/api/v1/data/showcase_task # control: 401 (correct)
Source
Extracted from the QA run #7627 (framework 92f26f7, console 6314e87f).
The degradation itself is honest and PASSES — please do not "fix" that half:
/ai/models,/conversations,/usage,/chatall 501 with the Cloud/EE remedy sentence (mounted-but-unimplemented, never 404/503);/ai/agentsreturns the declared envelope with the relocated payload; and/discovery'sservices.aimessage is string-identical to the 501 body. The defect below is only the ordering of the anonymous-deny gate.Symptom
Unauthenticated callers reach the
/ai/**family instead of being denied.GET /api/v1/ai/models→ 501 (leaking the Cloud/EE remedy sentence); unauthGET /api/v1/ai/agents→ 200. Reproduced twice.GET /api/v1/data/showcase_task→ 401, correctly.Disclosure impact is limited to two static strings, but the ordering contract this item exists to pin is inverted.
Root cause
runtime/src/domains/ai.tshandleAIRequestreturns the/ai/agentsempty-list courtesy ({ agents: [] }) andcapabilityUnavailable(deps, 'ai')at the top of the handler (aroundai.ts:68-79), while theshouldDenyAnonymousgate lives inside the per-route loop further down (ai.ts:128) — reachable only when the AI service is serveable. So on every open-edition deployment the whole/ai/**family answers unauthenticated callers before the anonymous gate is ever consulted. Still present onorigin/main(the courtesy +capabilityUnavailablereturn precedes theshouldDenyAnonymouscheck).Reproduction
On a stock boot, with no
Authorizationheader:Source
Extracted from the QA run #7627 (framework 92f26f7, console 6314e87f).