An open API service indexing awesome lists of open source software.

https://github.com/maximeroucher/heimdall

Self-discovering OWASP Top-10 pentest library for FastAPI apps
https://github.com/maximeroucher/heimdall

dast fastapi owasp pentest security

Last synced: 1 day ago
JSON representation

Self-discovering OWASP Top-10 pentest library for FastAPI apps

Awesome Lists containing this project

README

          

# Heimdall

**A self-discovering OWASP Top-10 pentest library for FastAPI applications.**

> Heimdall is the watchman of the Norse gods — he sees everything that approaches.
> Point Heimdall at a running FastAPI app and it *sees the app*: it learns the
> routes, the auth shape, and (optionally) the signing secrets, then runs the
> OWASP Top-10 exploit modules against the live target and writes a report.

It grew out of a hand-written pentest of one app and was generalized so the same
methodology runs against **any** FastAPI service with near-zero per-app wiring.

---

## Why it's generic

Most pentest scripts hard-code paths (`/core/auth/login`), credentials, and the
victim's data model. Heimdall doesn't. It **discovers** them:

| Concern | How Heimdall learns it |
|---|---|
| Every route, method, required-auth, params, body schema | `GET /openapi.json` (every FastAPI app serves it) |
| Login shape (JSON vs OAuth2 form vs `simple_token`) | Heuristic scoring over the OpenAPI operations |
| Register / "current user" endpoints | Same |
| Auth transport — bearer token **or** cookie-session | `securitySchemes` + the live login response (302/204 + `Set-Cookie` → cookie jar; `Authorization` scheme → token) |
| JWT algorithm & claim shape | Decodes a real token minted through the login flow |
| Candidate signing secrets, dependency stack | Optional white-box scan of the source tree (`--source`) |

Modules then operate off that discovered map — so a check written once ("secured
routes must reject anonymous callers") runs against any FastAPI app
unchanged. Auth-shape detection means the CSRF and session modules light up on a
cookie-session app and stay quiet on a token API, with no per-app configuration.

## Install

```bash
uv pip install -e '.[full]' # full = JWT forging + pip-audit + TOML on 3.10
# or minimal: uv pip install -e .
```

## Use

### CLI

```bash
# Black-box, discovery only (no attacks) — a safe first look:
heimdall --url http://127.0.0.1:8000 --discover-only

# Full assessment, white-box (source tree enables JWT-secret + component checks):
heimdall --url http://127.0.0.1:8000 \
--source /path/to/YourApp \
--cred admin:admin:admin@you.io:hunter2 \
--cred member:user:member@you.io:pw

# Or from a config file:
heimdall --config examples/example.toml

# Non-destructive pass, or scope to specific modules:
heimdall --url http://127.0.0.1:8000 --safe
heimdall --url http://127.0.0.1:8000 --only a01,a02
heimdall --url http://127.0.0.1:8000 --skip race,cmdi,xxe
heimdall --list-modules
```

#### Full flag reference

| Flag | Purpose |
|---|---|
| `--url URL` | Target base URL (required unless `--config` supplies it). |
| `--source PATH` | Target source tree — enables white-box checks (JWT-secret recovery, `a06` components, `sast`). |
| `--name NAME` | Friendly app name for the report. |
| `--config FILE` | TOML/JSON target config (CLI flags override its fields). |
| `--cred label:role:identifier:password` | A login credential; repeatable. `role` is free-form (`admin`, `user`, …) and drives BFLA/BOLA pairing. |
| `--launch CMD` / `--launch-cwd DIR` | Boot the target with a shell command first (and its working dir), then tear it down after. The target's own stdout/stderr is captured to a temp log and, on a failed boot, its tail is printed. |
| `--launch-timeout SECONDS` | How long to wait for a `--launch` target to answer (default 180). Raise it for apps that run migrations / seed fixtures on first boot. |
| `--spawn-db` / `--spawn-db-env VAR` | Spin up a throwaway DB matching the target's engine (Docker Postgres/MySQL/Mongo, or a SQLite file) and hand it to the target via `VAR` (default `SQLITE_DB`); torn down after. |
| `--spawn-db-kind KIND` | Force the throwaway engine (`sqlite`/`postgres`/`mysql`/`mongo`) instead of auto-detecting from the source. `sqlite` is the dependency-free choice for any app with a SQLite mode — no Docker, no port wiring. |
| `--db-url URL` | An existing throwaway SQLAlchemy URL to provision into (use `postgresql+psycopg2://…`). |
| `--provision N` / `--provision-admins N` | Insert N low-privilege (and N admin) test users straight into the DB, so BOLA/IDOR/BFLA checks have real cross-tenant subjects even when self-registration is closed. |
| `--no-mint` | Don't mint API-scoped JWTs even if the signing secret is recovered. |
| `--no-attacker` | Don't self-register a throwaway low-priv attacker account. |
| `--safe` | Skip every mutating/destructive probe. |
| `--only KEYS` / `--skip KEYS` | Comma-separated module keys to include / exclude. |
| `--fail-on LEVEL` | CI gate severity (`none,info,low,medium,high,critical`; default `high`). |
| `--baseline findings.json` | Suppress already-known findings; gate only on new ones. |
| `--discover-only` | Print the discovered profile (routes, auth, secrets) and exit — no attacks. |
| `--list-modules` | List registered modules and exit. |
| `--i-have-authorization` | Permit a non-loopback target (authorized use only). |
| `--out DIR` / `--no-color` | Report output dir (default `./heimdall-report`) / disable ANSI colour. |

#### Provisioning & throwaway targets

For a deep run against an app whose sign-up is closed (or to drive access-control
checks across *many* distinct owners), let Heimdall build the world:

```bash
# Boot the app onto a throwaway DB, seed real principals, then attack — all disposable:
heimdall --url http://127.0.0.1:8100 --source /path/to/App \
--launch '/path/to/App/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8100' \
--launch-cwd /path/to/App \
--spawn-db \
--provision 3 --provision-admins 1 \
--launch-timeout 300
```

Point `--launch` at the target's *own* interpreter (e.g. its `.venv/bin/uvicorn`)
so the boot command resolves the app's dependencies, and give `--launch-timeout`
enough room for an app that migrates/seeds on first boot.

`--provision` inserts distinct users (hashing passwords the way the app's own
model expects) and logs each in through the real login flow, giving the `a01`
BOLA/IDOR/BFLA probes genuine cross-user subjects. Combined with `--spawn-db` /
`--launch`, a whole assessment — server, database, users — is created and torn
down around a single command.

If the app has a built-in **SQLite mode**, add `--spawn-db-kind sqlite` — the
dependency-free throwaway (no Docker, no port wiring). Otherwise `--spawn-db`
auto-detects the engine.

Prefer a **server engine** (the app's real backend)? Use `--spawn-db-kind postgres`
(or `mysql`) instead — Heimdall spins a throwaway Docker server, reads the DB
connection variables the app actually uses from its source (env vars *and*
pydantic-settings / Django-style fields), and points the app at the throwaway
(folding the mapped port into the host when the app has no separate port var). If
it can't find a single connection variable to override, it **refuses** rather than
let the app boot against its real database — so a throwaway run never touches
production data.

### Library

```python
import heimdall

result = heimdall.assess(
base_url="http://127.0.0.1:8000",
source_path="/path/to/YourApp", # optional white-box
credentials=[("admin", "admin", "admin@you.io", "hunter2")], # label,role,id,pw
safe=False, # True → skip destructive probes
only={"a01", "a02"}, # or skip={"race", "cmdi"}
)
print(result.counts()) # {'CRITICAL': 1, 'HIGH': 3, 'SAFE': 9, ...}
print(result.report_paths) # (findings.json, REPORT.md, REPORT.html, findings.sarif)
```

Drive discovery alone:

```python
profile = heimdall.discover("http://127.0.0.1:8000", source_path="…")
print(heimdall.summarize(profile))
```

## Modules

26 detectors, grouped by OWASP category. Run `heimdall --list-modules` for the
live registry. Modules marked **⚡** are *destructive* (they mutate state) and are
skipped under `--safe`.

| Key | Title | What it does |
|---|---|---|
| **A01 — Broken Access Control** | | |
| `a01` | Access Control / IDOR | Unauth access to secured routes; BFLA on admin routes; BOLA/IDOR on object-by-id routes across owners |
| `csrf` | Cross-Site Request Forgery | On cookie-session apps: session-cookie `SameSite` + anti-CSRF-token enforcement (auto-skipped for token auth) |
| `data-exposure` | Excessive Data Exposure | Secrets/PII/other users' tokens in responses (own-token & public-metadata aware) |
| `mass-assignment` ⚡ | Mass Assignment | Over-posting privileged fields (`is_admin`, `role`) on create/update, with a determinism gate |
| `oidc` | OAuth / OIDC Flow | `state`/PKCE/`redirect_uri` handling on discovered OAuth flows |
| `open-redirect` | Open Redirect | Redirect params that navigate off-host (parsed like a browser, not by substring) |
| **A02 — Cryptographic Failures** | | |
| `a02` | JWT forging | `alg:none` forgery + weak-HS256-secret crack, *proven by live acceptance*; token-in-query leak |
| **A03 — Injection** | | |
| `a03` | Injection | SQLi (error/boolean, reproducibility-gated) + reflected-XSS + SSTI + traversal + operator injection |
| `sqli-blind` ⚡ | Blind SQLi | Time-based blind SQL injection |
| `cmdi` ⚡ | OS Command Injection | Time-based OS command injection |
| `xxe` ⚡ | XML External Entity | XXE file disclosure (structural passwd-line match) on XML-accepting routes |
| `sast` | SAST → DAST chaining | Source-sink scan whose findings are re-probed live against the running app |
| **A04 — Insecure Design** | | |
| `business-logic` ⚡ | Numeric Abuse | Negative/overflow quantities & price/amount tampering |
| `race` ⚡ | Race / TOCTOU | Concurrent double-spend; flags only *divergent* (cumulative) races, not idempotent ones |
| `workflow` ⚡ | Multi-step Replay | Replaying a step for a double effect |
| `resource-consumption` | Resource Consumption | Unbounded pagination / payload size / missing rate limits |
| **A05 — Security Misconfiguration** | | |
| `a05` | Misconfiguration | CORS reflection, missing security headers, docs exposure, stack-trace/debug leaks |
| `host-header` | Host Header Attacks | Host/`X-Forwarded-Host` poisoning reflected into URLs |
| `improper-inventory` | Sensitive Paths | Exposed `.env`/config/backup/VCS & undocumented admin surfaces |
| `graphql` | GraphQL Exposure | Introspection, suggestions, unauth queries |
| `websocket` | WebSocket Security | Origin/auth enforcement on discovered WS endpoints |
| `file-upload` ⚡ | Unrestricted Upload | Dangerous file types accepted on upload sinks |
| **A06 — Vulnerable Components** | | |
| `a06` | Outdated Components | `pip-audit` + heuristic version checks (needs `--source`) |
| **A07 — Authentication Failures** | | |
| `a07` | Auth Failures | Login rate-limit + XFF bypass, user enumeration, weak passwords, lockout (429/423) vs OAuth-token endpoints |
| `session` | Session Lifecycle | Token/session expiry, logout invalidation, rotation |
| **A10 — SSRF** | | |
| `a10` | SSRF | URL-accepting sink discovery + internal-target / cloud-metadata probes |

Each module also emits **TESTED-SAFE** findings so the report distinguishes
*verified-safe* from *untested*.

## Output

Each run writes to `heimdall-report/`:
- `REPORT.md` — executive summary, **attack chains**, per-finding OWASP mapping,
indicative CVSS, evidence/PoC, reproduction, references, tools.
- `REPORT.html` — the same, standalone + styled (shareable).
- `findings.json` — machine-readable.
- `findings.sarif` — SARIF 2.1.0 for GitHub/GitLab/Azure code-scanning
(real findings as `kind:fail`, TESTED-SAFE as `kind:pass`).

## CI integration

```bash
# fail the build on any finding at/above a threshold (default: high)
heimdall --url http://127.0.0.1:8000 --source . --fail-on high

# regression gate: only fail on findings NOT already in a baseline report
heimdall --url http://127.0.0.1:8000 --baseline known-findings.json --fail-on medium
```

`--fail-on {none,info,low,medium,high,critical}` sets the exit-code gate;
`--baseline ` suppresses already-known/accepted findings so CI
only breaks on *new* ones. Upload `findings.sarif` with
`github/codeql-action/upload-sarif` to surface results in the Security tab.

## Try it — bundled vulnerable demo

The repo ships a deliberately-insecure FastAPI app so you can see Heimdall work
end-to-end without pointing it at anything of your own:

```bash
pip install -e '.[demo]'
uvicorn examples.vulnerable_app.main:app --host 127.0.0.1 --port 8099
heimdall --url http://127.0.0.1:8099 --source examples/vulnerable_app \
--cred admin:admin:admin:admin123 --cred alice:user:alice:alice123
```

A clean run reports **~1 CRITICAL + 7-8 HIGH** — JWT-secret forgery, BOLA/IDOR,
BFLA, reflected XSS, SSRF, credentialed CORS, PII/secret exposure, and more —
alongside TESTED-SAFE verdicts on the app's few correct endpoints. Every finding
maps to a planted flaw documented in
[`examples/vulnerable_app/README.md`](examples/vulnerable_app/README.md).

## Safety & scope

- **Guardrail:** Heimdall refuses any non-loopback target unless you pass
`--i-have-authorization`.
- `--safe` skips every mutating/destructive probe.
- Reports may contain secrets and target internals — the repo `.gitignore`
excludes them; keep them out of version control.

### ⚖️ Authorized use only

This is a security-testing tool. **Use it only against systems you own or for
which you have prior, explicit, written authorization to test.** Unauthorized
scanning or exploitation of systems you do not own is illegal in most
jurisdictions (e.g. the US Computer Fraud and Abuse Act, EU Directive 2013/40/EU,
the UK Computer Misuse Act 1990, and equivalents). By using this software you
confirm you have the necessary authorization for every target and accept sole
responsibility for your use of it; the authors accept no liability for misuse.
See the [LICENSE](LICENSE) for the full disclaimer.

## How it fits together

```
heimdall/
discovery/ openapi → RouteMap · auth detection (token/cookie) · JWKS · source secret scan → AppProfile
bootstrap/ launch target · spawn throwaway DB · provision users · mint principals via the real login flow
modules/ 26 detectors, each reading the AppProfile — no hard-coded paths
core/ Context, Finding, report renderer (MD/HTML/SARIF), attack-chain builder, loopback guardrail
runner.py launch → discover → bootstrap → modules → report
```

## Status

Alpha. The discovery layer targets FastAPI/OpenAPI 3.x; the exploit modules are
black-box-first and conservative about false positives (unconfirmed SSRF sinks,
heuristic enumeration, etc. are labelled as needing manual/OAST confirmation).
A 60+-case test suite (`pytest`) pins each module's precision — every
false-positive fix ships with a false-negative control and a regression test.