Fix BOLA by enforcing per-user ownership for tasks and reports#484
Merged
Conversation
Introduce a per-user/per-workspace ownership column on the tasks, findings, and reports tables to close the BOLA gap in issue utksh1#401, where any caller could address another user's resources by ID. - auth.resolve_owner_id / get_current_owner derive a stable owner identity from the authenticated-user header (X-User-Id), falling back to a shared DEFAULT_OWNER_ID for single-user deployments. - owner_id columns are added idempotently (NOT NULL DEFAULT 'default') so existing rows are backfilled and single-user clients keep their history. - Migration 003 adds owner indexes and a defensive backfill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Populate owner_id at creation time and scope every read, list, delete, and report/export endpoint to the requesting owner so cross-user access is impossible (BOLA, issue utksh1#401). - executor.create_task accepts owner_id; findings and reports inherit the owning task's owner_id on every execution path (manual, modular scanner, workflow, scheduler). - task/report/finding GET/stream/cancel endpoints return 403 on owner mismatch; single delete stays idempotent for missing tasks but 403s on a foreign-owned task. - list/aggregate endpoints (tasks, findings, reports, dashboard, attack-surface, assets) filter by owner_id and namespace their caches by owner so one user's data is never listed or served to another. - bulk delete and clear operate only on the caller's own tasks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verify two distinct users (via X-User-Id) cannot reach each other's data: fetch/stream/cancel/delete/report all return 403 across owners, list and dashboard endpoints never leak another owner's tasks/findings/reports, and bulk delete/clear only touch the caller's own tasks. Also confirm owners retain full access to their own resources and missing-task delete stays idempotent (issue utksh1#401). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
@madsysharma please check once, please add all relevant tags as well |
utksh1
approved these changes
Jun 4, 2026
Owner
utksh1
left a comment
There was a problem hiding this comment.
Reviewed the owner_id schema/migration, request owner resolution, endpoint scoping, cache namespacing, and cross-user integration tests. This closes the BOLA exposure while preserving default single-user behavior. Approved for merge once branch is current.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #401.
Tasks, findings, and reports were globally addressable by ID with no
per-user ownership check — any authenticated caller could read, list,
delete, or export another user's scan results (Broken Object Level
Authorization / BOLA).
This PR introduces a per-user/per-workspace ownership model and scopes every
task/finding/report operation to the requesting owner.
Approach
SecuScan authenticates the deployment with a single shared API key. That gate
doesn't distinguish the users/workspaces sharing a deployment, which is what
enabled BOLA. We derive a stable owner identity per request from the
authenticated-user header (
X-User-Id) — the same header the existingresolve_client_identityalready treats as the authenticated user — and fallback to a shared
DEFAULT_OWNER_IDfor single-user deployments. In productionthe header is expected to be set by an upstream auth proxy / SSO layer.
Key changes
Schema (
database.py,migrations/003_add_owner_id.sql)owner_id TEXT NOT NULL DEFAULT 'default'added totasks,findings,reports.(SQLite has no
ADD COLUMN IF NOT EXISTS); the NOT NULL default backfillsexisting rows to the shared default owner.
Auth (
auth.py)resolve_owner_id/get_current_ownerdependency +DEFAULT_OWNER_ID.Creation (
executor.py,routes.py)create_taskacceptsowner_id;POST /task/startpersists the requester.owner_idon every execution path.Authorization (
routes.py)task/{id}/status|stream|result|cancel|report/{csv,html,pdf,sarif}→ 403 onowner mismatch.
foreign-owned task; bulk delete and clear operate only on the caller's tasks.
finding/{id}→ 403 on mismatch.tasks,findings,reports,dashboard/summary,attack-surface,assetsfilter byowner_id; cached lists are namespaced by owner so oneuser's data is never served to another.
Tests (
test_owner_authorization.py)task, can't see A's tasks/findings/reports in any list/dashboard, and bulk
delete/clear never touch A's data; User A retains full access to their own.
Migration / deployment notes
X-User-Idshare
owner_id = 'default'and keep their current behaviour.owner_idcolumns are added automatically on startup — no manual step.X-User-Id.DELETE /tasks/clearnow purges only the caller's history(the previous blind data-directory wipe was removed as it crossed owners).
Testing
python -m pytest testing/backend -m "not benchmark"→ 937 passed, 3 skipped.(One unrelated Windows-only
chmodpermission test fails locally; passes onLinux CI.)
ruff checkclean.🤖 Generated with Claude Code