Metadata-Version: 2.4
Name: supabase-credential-assessor
Version: 1.0.0
Summary: Safe-by-default impact-assessment tool for a leaked Supabase credential.
Author-email: "Anuj Rawat (Tazer)" <ajcoolx619@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/ajtazer/ajtazer/tree/main/tools/supabase-cred-assessment
Project-URL: Repository, https://github.com/ajtazer/ajtazer
Project-URL: Issues, https://github.com/ajtazer/ajtazer/issues
Project-URL: Changelog, https://github.com/ajtazer/ajtazer/blob/main/tools/supabase-cred-assessment/CHANGELOG.md
Keywords: supabase,security,assessment,credential,appsec
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Information Technology
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: httpx>=0.27
Requires-Dist: rich>=13.7
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: ruff==0.8.6; extra == "dev"
Requires-Dist: black==24.10.0; extra == "dev"
Requires-Dist: build==1.2.2.post1; extra == "dev"
Requires-Dist: twine==6.0.1; extra == "dev"
Dynamic: license-file

# 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. `SELECT` fetches 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 → `PATCH` with an impossible filter (matches **zero** rows)
  - DELETE → `DELETE` with 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.
- **`--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.log` and the console.

## Install

```bash
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

```bash
# 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`](web/index.html).
Open it directly in a browser (or serve the folder) — there is **no build step
and no backend**:

```bash
# 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

1. **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.
2. **Fingerprint the project** (Phase 2) via the PostgREST root, which also
   yields the exposed schema and server metadata.
3. **Map the surface, safely.** The PostgREST OpenAPI document (Phase 4) lists
   every exposed table/view/RPC without touching `information_schema` or any
   data. Each relation is then probed with a single `SELECT … LIMIT 1` that
   records **column names only**.
4. **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.
5. **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:

```bash
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:

```bash
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

```bash
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`](../../.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`**.

1. Update the version in `supabase_assessor/__init__.py` (`__version__`) and add
   a `CHANGELOG.md` entry.
2. Commit the changes.
3. Push a version tag:
   ```bash
   git tag supabase-credential-assessor-v1.2.3
   git push origin supabase-credential-assessor-v1.2.3
   ```
4. GitHub Actions builds the sdist + wheel, runs `twine check`, generates
   `SHA256SUMS`, and creates a GitHub Release with auto-generated notes and the
   assets attached.
5. A final version (`X.Y.Z`) is published to **PyPI**; a pre-release such as
   `1.2.3rc1` is 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`, workflow `release.yml`, and
> environment `pypi-supabase-credential-assessor` (respectively
> `testpypi-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.
