Problem
The agent-registry surface currently leaks existence: an unauthorized caller can distinguish "agent ID does not exist" (404) from "agent ID exists but you do not own it" (403). This affects every route that fetches a registry record, checks it exists, then authorizes.
Affected routes in routes/agent_auth_requests.py:
create_scope_request — fetches registry record → 404 on unknown → require_owner_or_admin → 403 on not-owned
approve_scope_request — same pattern
deny_scope_request — same pattern
approve_auth_request (consent approve) — same pattern
deny_auth_request (consent deny) — same pattern
The GET /api/agents/registry/{id} route in routes/agent_registry.py already documents "owner or admin; else 404" — it is existence-hiding. The scope-request and consent routes should match that.
Context
PR #1921 added the scope-request flow. During review jaylfc noted the 404-vs-403 pattern and wrote (#1921 (comment)):
The 404-unknown vs 403-not-owner distinction is the file-wide pattern and matches the sibling consent approve/deny, so making only these two routes existence-hiding would introduce a new inconsistency rather than remove one. [...] happy to switch the whole agent-registry surface to existence-hiding 404 as a separate pass if preferred.
The create-path reorder in #1921 moved authz before the scope-vocabulary check, but not before the record-existence check — so the leak remains.
Proposed solution
Make every agent-registry route existence-hiding: return 404 for both "does not exist" and "exists, not yours". The pattern is:
- Fetch the registry record (without revealing whether it exists)
- Authorize the caller (owner/admin check)
- If authz fails → 404 (same as "not found")
- Only then proceed with scope validation, grant writes, etc.
The GET /api/agents/registry/{id} route already does this — the scope-request and consent routes should follow the same pattern.
Affected code
tinyagentos/routes/agent_auth_requests.py — the scope-request create/approve/deny handlers and the consent approve/deny handlers
- Tests that assert 403 on not-owned will need to become 404 assertions
References
Problem
The agent-registry surface currently leaks existence: an unauthorized caller can distinguish "agent ID does not exist" (404) from "agent ID exists but you do not own it" (403). This affects every route that fetches a registry record, checks it exists, then authorizes.
Affected routes in
routes/agent_auth_requests.py:create_scope_request— fetches registry record → 404 on unknown →require_owner_or_admin→ 403 on not-ownedapprove_scope_request— same patterndeny_scope_request— same patternapprove_auth_request(consent approve) — same patterndeny_auth_request(consent deny) — same patternThe
GET /api/agents/registry/{id}route inroutes/agent_registry.pyalready documents "owner or admin; else 404" — it is existence-hiding. The scope-request and consent routes should match that.Context
PR #1921 added the scope-request flow. During review jaylfc noted the 404-vs-403 pattern and wrote (#1921 (comment)):
The create-path reorder in #1921 moved authz before the scope-vocabulary check, but not before the record-existence check — so the leak remains.
Proposed solution
Make every agent-registry route existence-hiding: return 404 for both "does not exist" and "exists, not yours". The pattern is:
The
GET /api/agents/registry/{id}route already does this — the scope-request and consent routes should follow the same pattern.Affected code
tinyagentos/routes/agent_auth_requests.py— the scope-request create/approve/deny handlers and the consent approve/deny handlersReferences
GET /api/agents/registry/{id}inroutes/agent_registry.py— reference implementation of existence-hiding 404