Labels: security, multi-tenant, priority-high
Severity: S0/S1. List endpoints scope by team but single-resource access does not -- any authenticated user reaches any team's data by id.
| Finding (path:line) |
Issue |
modules/malware/api_router.py:1090-1143 (+25 more) |
Single-resource GET/PATCH/DELETE do del request, auth and look up by primary key only -> any authenticated user reads/mutates any team's targets, investigations, branches, outcomes, observations, playbooks by UUID |
api/routers/findings_workflow.py + :280 |
Finding workflow transitions and evidence-chain use finding_id with no team/ownership check -> any operator transitions any team's finding |
api/routers/tags.py |
Tag assign/list/delete look up system_id with no team scope -> IDOR across teams |
api/routers/dashboard.py, topology.py, search.py, executive.py |
No team scoping -> any reader sees fleet-wide systems/connections, searches all teams, and downloads fleet-wide risk PDFs + per-system evidence ZIPs |
api/middleware/idempotency.py |
Idempotency cache key is IDEM:{key} with no user scope -> user A replays user B's cached response by guessing the key |
modules/malware/api_router.py:979 |
upload_target does contents = await sample.read() with no size guard -> a multi-GB POST OOMs the uvicorn worker (trivial DoS) |
api/routers/cost.py:269 |
Admin check uses getattr(auth,'is_admin',False) instead of auth.role == 'admin' -> inconsistent guard that may bypass depending on AuthContext shape |
Acceptance: every single-resource handler resolves the row with a team/ownership predicate (not bare PK); idempotency keys namespaced by principal; uploads size-capped and streamed; the admin check uses the canonical role field everywhere.
Labels: security, multi-tenant, priority-high
Severity: S0/S1. List endpoints scope by team but single-resource access does not -- any authenticated user reaches any team's data by id.
modules/malware/api_router.py:1090-1143(+25 more)del request, authand look up by primary key only -> any authenticated user reads/mutates any team's targets, investigations, branches, outcomes, observations, playbooks by UUIDapi/routers/findings_workflow.py+:280finding_idwith no team/ownership check -> any operator transitions any team's findingapi/routers/tags.pysystem_idwith no team scope -> IDOR across teamsapi/routers/dashboard.py,topology.py,search.py,executive.pyapi/middleware/idempotency.pyIDEM:{key}with no user scope -> user A replays user B's cached response by guessing the keymodules/malware/api_router.py:979upload_targetdoescontents = await sample.read()with no size guard -> a multi-GB POST OOMs the uvicorn worker (trivial DoS)api/routers/cost.py:269getattr(auth,'is_admin',False)instead ofauth.role == 'admin'-> inconsistent guard that may bypass depending on AuthContext shapeAcceptance: every single-resource handler resolves the row with a team/ownership predicate (not bare PK); idempotency keys namespaced by principal; uploads size-capped and streamed; the admin check uses the canonical role field everywhere.