From 938c2eb0be59b86514e3c75e5f2af1ef977d27bb Mon Sep 17 00:00:00 2001 From: 1445043649 <> Date: Sun, 28 Jun 2026 20:19:20 +0800 Subject: [PATCH] harden(gateway): uniform 404 on API key revoke to hide cross-tenant existence (Stage 1 PR4) Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/gateway/routers/api_keys.py | 4 +++- backend/tests/test_api_keys_router.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/backend/app/gateway/routers/api_keys.py b/backend/app/gateway/routers/api_keys.py index 4662bee5..b08f1949 100644 --- a/backend/app/gateway/routers/api_keys.py +++ b/backend/app/gateway/routers/api_keys.py @@ -104,6 +104,8 @@ async def revoke_api_key( key = await key_repo.get(key_id) if key is None: raise HTTPException(status_code=404, detail="api key not found") - await _require_sa_in_workspace(key["service_account_id"], sa_repo) + sa = await sa_repo.get(key["service_account_id"]) + if sa is None or sa["workspace_id"] != _current_workspace_id(): + raise HTTPException(status_code=404, detail="api key not found") await key_repo.revoke(key_id) return Response(status_code=204) diff --git a/backend/tests/test_api_keys_router.py b/backend/tests/test_api_keys_router.py index 1b708e59..d763f68a 100644 --- a/backend/tests/test_api_keys_router.py +++ b/backend/tests/test_api_keys_router.py @@ -157,3 +157,19 @@ async def test_create_for_suspended_sa_409(tmp_path): assert r.status_code == 409 finally: await _cleanup() + + +async def test_revoke_404_bodies_are_indistinguishable(tmp_path): + await _init_db_with_sa(tmp_path, sa_id="sa-1", workspace_id="w-1") + try: + client_a = TestClient(_make_app(workspace_id="w-1")) + created = client_a.post("/api/v1/api-keys", json={"service_account_id": "sa-1", "name": "k", "scopes": ""}).json() + client_b = TestClient(_make_app(workspace_id="w-2")) + # cross-workspace existing key, and a non-existent key, must return identical 404 bodies + cross = client_b.delete(f"/api/v1/api-keys/{created['id']}") + missing = client_b.delete("/api/v1/api-keys/does-not-exist") + assert cross.status_code == 404 + assert missing.status_code == 404 + assert cross.json() == missing.json() + finally: + await _cleanup()