Skip to content

fix(api): scope /hunts queries by tenant_id (C-2)#117

Merged
beenuar merged 2 commits into
mainfrom
fix/hunts-tenant-isolation
May 14, 2026
Merged

fix(api): scope /hunts queries by tenant_id (C-2)#117
beenuar merged 2 commits into
mainfrom
fix/hunts-tenant-isolation

Conversation

@beenuar
Copy link
Copy Markdown
Owner

@beenuar beenuar commented May 14, 2026

Summary

Closes the cross-tenant data-access vulnerability in the /hunts API
(BUGHUNT.md C-2). Previously every SELECT/UPDATE against aisoc_hunts and
aisoc_hunt_runs ran without a tenant_id filter, so any authenticated caller
could read or mutate any other tenant's hunt hypotheses, runs, and findings
just by guessing UUIDs. aisoc_hunt_runs didn't even carry a tenant_id
column.

Changes

  • services/api/app/api/v1/endpoints/hunts.py — every list/get/update/
    create/run/add-findings path now binds :tenant_id from the calling
    CurrentUser and filters on tenant_id = :tenant_id (or includes it in
    INSERTs). Cross-tenant reads now return 404 instead of leaking data;
    cross-tenant writes return 404 via RETURNING-checks. Also replaces
    Postgres-specific cast syntax (:tags::text[], :sample::jsonb,
    :findings::jsonb) with CAST(... AS ...) so SQLAlchemy's text() can
    bind the parameters reliably (previous form silently broke parameter
    binding). created_by now records the user's email instead of the
    str(CurrentUser) repr.
  • services/api/migrations/043_hunts_tenant_isolation.sql — add
    tenant_id column to aisoc_hunt_runs, backfill from the parent
    aisoc_hunts row, and add covering indexes on (tenant_id, created_at DESC)
    and (tenant_id, hunt_id, run_at DESC).
  • services/api/tests/test_hunts_tenant_isolation.py — regression tests
    that mock DBSession, capture every executed SQL statement, and assert
    tenant_id is in the WHERE clause and bound params on every touch of
    aisoc_hunts* tables. Cross-tenant reads/updates/runs return 404.

Verification

  • python -m pytest tests/test_hunts_tenant_isolation.py -x -q9 passed
  • python -m pytest -x -q (full api suite) → 1212 passed

Refs

  • BUGHUNT.md C-2 (Critical) — Cross-tenant access in /hunts endpoints

Made with Cursor

…ccess (C-2)

The /hunts API previously executed every SELECT/UPDATE against aisoc_hunts and
aisoc_hunt_runs without a tenant_id filter, so any authenticated caller could
read or mutate any other tenant's hunt hypotheses, runs, and findings simply by
guessing or enumerating UUIDs. The aisoc_hunt_runs table didn't even carry a
tenant_id column.

Changes:
- services/api/app/api/v1/endpoints/hunts.py: every list/get/update/create/run/
  add-findings path now binds `:tenant_id` from the calling CurrentUser and
  filters on `tenant_id = :tenant_id` (or includes it in INSERTs). Cross-tenant
  reads return 404 instead of leaking data; cross-tenant writes return 404 via
  RETURNING-checks. Also replace PG-specific cast syntax (`:tags::text[]`,
  `:sample::jsonb`, `:findings::jsonb`) with `CAST(... AS ...)` so SQLAlchemy's
  text() can bind the parameters reliably. `created_by` now records the user's
  email instead of the str(CurrentUser) repr.
- services/api/migrations/043_hunts_tenant_isolation.sql: add tenant_id column
  to aisoc_hunt_runs, backfill from parent aisoc_hunts row, and add covering
  indexes on (tenant_id, created_at DESC) and (tenant_id, hunt_id, run_at DESC).
- services/api/tests/test_hunts_tenant_isolation.py: regression tests that mock
  DBSession, capture every executed SQL statement, and assert tenant_id is in
  the WHERE clause and bound params on every touch of aisoc_hunts* tables.
  Cross-tenant reads/updates/runs return 404. Full api suite passes (1212/1212).

Refs: BUGHUNT.md C-2
Restores 'Python — Lint & Type-check' to green by satisfying the
repo-wide ruff format gate that runs across all services in CI.
@beenuar beenuar marked this pull request as ready for review May 14, 2026 10:46
@beenuar beenuar merged commit 6377aba into main May 14, 2026
25 checks passed
@beenuar beenuar deleted the fix/hunts-tenant-isolation branch May 14, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant