Safe-by-default impact-assessment tool for a leaked Supabase credential.
Project description
Supabase Credential Impact Assessor
A safe-by-default, read-only command-line tool for inventorying a leaked Supabase credential and assessing its blast radius during an authorized security assessment.
⚠️ Authorized use only. This tool is intended for internal product security teams triaging a credential leak they are authorized to assess. It defaults to non-destructive, read-only checks. Any write/delete or account creation behaviour is disabled unless you explicitly opt in.
The goal is impact assessment, not exploitation: quickly determine what a leaked key can reach so the owning product team can rotate it and remediate.
Features
Nine phases, mapped to the assessment workflow:
| Phase | Name | What it does | Network |
|---|---|---|---|
| 1 | Credential Identification | Decode the JWT (header, payload, role, issuer, audience, expiry, project ref, custom claims); classify key kind | none |
| 2 | Project Discovery | Fingerprint project URL, ref, region hint, API version, server headers | read-only |
| 3 | Permission Enumeration | Reachability of REST, Storage, Edge Functions, Auth, Realtime | read-only |
| 4 | Database Enumeration | List tables/views/RPCs via PostgREST OpenAPI; SELECT … LIMIT 1 |
read-only* |
| 5 | Storage Enumeration | List buckets, classify public/private, list objects | read-only* |
| 6 | Auth Enumeration | Signup/magic-link/reset/admin exposure via GoTrue settings | read-only* |
| 7 | Privilege Matrix | Per-resource SELECT/INSERT/UPDATE/DELETE/EXEC/Storage/Realtime grid | — |
| 8 | Evidence Collection | Write report.md, report.html, raw.json, summary.json, logs/, screenshots/ |
— |
| 9 | Attribution Assistance | Collect non-sensitive identifiers to help owners trace the key | — |
* Optional intrusive validation is off by default — see below.
Safety model
- Default: only read-only and metadata probes run.
SELECTfetches at most one row and records column names only, never values. --write-probes: enables non-destructive write validation:- INSERT →
POST [](empty array, inserts zero rows) - UPDATE →
PATCHwith an impossible filter (matches zero rows) - DELETE →
DELETEwith an impossible filter (matches zero rows) - Storage → uploads one uniquely-named temp object and immediately deletes it; RPCs are probed with an invalid argument signature so the function body never executes.
- INSERT →
--active: enables auth actions that can create a real user (signup). Off by default; use only with explicit authorization.- The raw key is never written to disk — only a truncated SHA-256 fingerprint is stored in the evidence.
- Every request is logged to
logs/assessment.logand the console.
Install
cd tools/supabase-cred-assessment
python3.12 -m venv .venv && source .venv/bin/activate
pip install -e .
# or, without installing:
pip install -r requirements.txt
Usage
# Safe, read-only assessment (recommended default). Key is prompted securely.
supabase-assess --url https://YOURREF.supabase.co
# Provide the key non-interactively and choose an output directory.
supabase-assess -u https://YOURREF.supabase.co -k "<leaked-key>" -o ./out
# Opt into non-destructive write/delete validation.
supabase-assess -u https://YOURREF.supabase.co --write-probes
# Opt into auth actions that can create a real user (authorized only).
supabase-assess -u https://YOURREF.supabase.co --write-probes --active
# Run as a module:
python -m supabase_assessor --url https://YOURREF.supabase.co
Output
Written to the output directory (default ./assessments/supabase-<timestamp>/):
report.md Human-readable Markdown report
report.html Self-contained styled HTML report
raw.json Complete assessment: every probe and result
summary.json Compact machine-readable summary
logs/ assessment.log — full request audit trail
screenshots/ Placeholder for manual evidence
Each report contains: Executive Summary, Technical Findings, Risk Rating, Affected Resources (privilege matrix), Credential Details, Evidence (probe log), Attribution Assistance, and Recommended Mitigation.
Web UI (no install, browser-only)
A static, dependency-free web edition lives at web/index.html.
Open it directly in a browser (or serve the folder) — there is no build step
and no backend:
# Just open it:
xdg-open web/index.html # or: open web/index.html (macOS)
# ...or serve the folder locally:
python -m http.server -d web 8000 # then visit http://localhost:8000
Paste the Supabase URL and key and click Run assessment. The JWT is decoded
and all read-only phases run client-side; results, the privilege matrix, and
downloadable raw.json / report.md / report.html are produced in the tab.
The key stays in memory only and is never stored or transmitted anywhere except
to the project you name.
Browser limitation — graceful degradation: some probes depend on the target
returning permissive CORS headers. If the browser blocks a response, that probe
is marked ⨯ browser/CORS blocked and, if everything is blocked, the UI shows
the equivalent CLI command so you can run the full Python tool instead.
How the assessment works
- Decode, don't guess. The key is first decoded locally (Phase 1). Supabase legacy keys are JWTs, so the role/issuer/expiry/project-ref are read straight from the token with zero network traffic.
- Fingerprint the project (Phase 2) via the PostgREST root, which also yields the exposed schema and server metadata.
- Map the surface, safely. The PostgREST OpenAPI document (Phase 4) lists
every exposed table/view/RPC without touching
information_schemaor any data. Each relation is then probed with a singleSELECT … LIMIT 1that records column names only. - Classify every outcome through one shared mapper so
SELECT, storage, and auth results all speak the same vocabulary — that consistency is what makes the Phase 7 privilege matrix trustworthy. - Score and report. Findings and an overall risk rating are derived from the probe results and written to JSON/Markdown/HTML evidence (Phase 8), plus non-sensitive attribution identifiers (Phase 9) the owning team can trace.
Writes are never attempted unless you opt in, and even then only via provably zero-effect techniques (empty-array insert, impossible-filter update/delete, temp-object upload+delete, non-executing RPC signature probe).
Build a release artifact
The CLI is a standard PEP 517 project (setuptools backend). To build the sdist + wheel that you can attach to a GitHub Release:
cd tools/supabase-cred-assessment
python -m pip install --upgrade build
python -m build # writes dist/*.tar.gz and dist/*.whl
Install the built wheel anywhere with:
pip install dist/supabase_credential_assessor-1.0.0-py3-none-any.whl
(dist/ is git-ignored so build outputs never land in the repo.)
Development
pip install -e ".[dev]"
pytest # offline unit tests (JWT decode + classifier)
ruff check supabase_assessor # lint
black --check supabase_assessor # formatting
mypy supabase_assessor # type check (advisory)
Releasing
Releases are automated by GitHub Actions
(.github/workflows/release.yml). Because
this repository ships two independent packages, each is released with its own
tag prefix — this package uses supabase-credential-assessor-vX.Y.Z.
- Update the version in
supabase_assessor/__init__.py(__version__) and add aCHANGELOG.mdentry. - Commit the changes.
- Push a version tag:
git tag supabase-credential-assessor-v1.2.3 git push origin supabase-credential-assessor-v1.2.3
- GitHub Actions builds the sdist + wheel, runs
twine check, generatesSHA256SUMS, and creates a GitHub Release with auto-generated notes and the assets attached. - A final version (
X.Y.Z) is published to PyPI; a pre-release such as1.2.3rc1is published to TestPyPI. Both use OIDC Trusted Publishing (no API tokens).
Alternatively, run the Manual Release workflow from the Actions tab
(workflow_dispatch) to bump the version, test, build, and release — with
prerelease / draft / publish-to-index toggles.
One-time setup: on PyPI (and TestPyPI) add a Trusted Publisher for this package pointing at repo
ajtazer/ajtazer, workflowrelease.yml, and environmentpypi-supabase-credential-assessor(respectivelytestpypi-supabase-credential-assessor). Each package uses its own environment so the two configs stay distinct — PyPI rejects duplicate pending-publisher configs.
Architecture
supabase_assessor/
models.py Typed dataclasses (single source of truth)
http_client.py Async httpx client: logging + retry/backoff
classify.py Status-code -> AccessResult normalisation
logging_config.py Rich console + file logging
phase1_jwt.py Phase 1 (offline)
phase2_discovery.py Phase 2
phase3_permissions.py Phase 3
phase4_database.py Phase 4
phase5_storage.py Phase 5
phase6_auth.py Phase 6
phase7_matrix.py Phase 7 (matrix rendering)
phase9_attribution.py Phase 9
analysis.py Findings + overall risk rating
reporting.py Phase 8 (JSON/Markdown/HTML)
orchestrator.py Runs all phases with a progress bar
cli.py Typer CLI entry point
web/index.html Static, browser-only edition (no build step)
tests/ Offline unit tests
pyproject.toml Packaging metadata (PEP 517, setuptools)
License
MIT. Provided for authorized defensive security assessment only.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file supabase_credential_assessor-1.0.0.tar.gz.
File metadata
- Download URL: supabase_credential_assessor-1.0.0.tar.gz
- Upload date:
- Size: 38.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ee9840ccde879da47c86f21fb7e75c9bc660647f7a5bf27a71b7c28723e76b5
|
|
| MD5 |
8021979844ec08e1b5c2504008655c59
|
|
| BLAKE2b-256 |
3809d82e85a8c0c5a112597d35777026dd2772d81157ee7cf099c3da007d43b9
|
Provenance
The following attestation bundles were made for supabase_credential_assessor-1.0.0.tar.gz:
Publisher:
release.yml on ajtazer/ajtazer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
supabase_credential_assessor-1.0.0.tar.gz -
Subject digest:
3ee9840ccde879da47c86f21fb7e75c9bc660647f7a5bf27a71b7c28723e76b5 - Sigstore transparency entry: 2211717425
- Sigstore integration time:
-
Permalink:
ajtazer/ajtazer@0f8300ded775c4f990f984dab0c89e7d46de4cae -
Branch / Tag:
refs/tags/supabase-credential-assessor-v1.0.0 - Owner: https://github.com/ajtazer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0f8300ded775c4f990f984dab0c89e7d46de4cae -
Trigger Event:
push
-
Statement type:
File details
Details for the file supabase_credential_assessor-1.0.0-py3-none-any.whl.
File metadata
- Download URL: supabase_credential_assessor-1.0.0-py3-none-any.whl
- Upload date:
- Size: 42.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36f947cddca64beceda01894f598df70cd3515683f0ddc817142c4de9cbbe073
|
|
| MD5 |
237de6d846b2f570ab8f94f2da2f5571
|
|
| BLAKE2b-256 |
fc6ea5e95ca16a2da5d4240ea6c779c5e08833c29f0472c087ed9edeb63a8a4b
|
Provenance
The following attestation bundles were made for supabase_credential_assessor-1.0.0-py3-none-any.whl:
Publisher:
release.yml on ajtazer/ajtazer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
supabase_credential_assessor-1.0.0-py3-none-any.whl -
Subject digest:
36f947cddca64beceda01894f598df70cd3515683f0ddc817142c4de9cbbe073 - Sigstore transparency entry: 2211717439
- Sigstore integration time:
-
Permalink:
ajtazer/ajtazer@0f8300ded775c4f990f984dab0c89e7d46de4cae -
Branch / Tag:
refs/tags/supabase-credential-assessor-v1.0.0 - Owner: https://github.com/ajtazer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0f8300ded775c4f990f984dab0c89e7d46de4cae -
Trigger Event:
push
-
Statement type: