Authorization contract testing for IDOR/BOLA — prove user A can't touch user B's objects, in CI.
Project description
How it works · Quickstart · Contract · CI guarantees · Roadmap
How it works
flowchart LR
Contract["1. Contract<br/>actors + IDs + endpoint rules"]
Matrix["2. Generate matrix<br/>endpoint x object x declared actor"]
Safety["3. Safety gate<br/>record unsafe skips + preflight allow rows"]
Replay["4. Live API<br/>replay executable deny rows"]
Verdict{"Match contract?"}
Setup["Exit 2<br/>invalid or untrustworthy setup"]
Finding["Exit 1<br/>BOLA, leak, or strict warning"]
Clean["Exit 0<br/>no failing executed checks<br/>warnings and skips stay visible"]
Contract -->|valid| Matrix
Contract -->|invalid| Setup
Matrix --> Safety
Safety -->|preflight fails| Setup
Safety -->|passes| Replay
Replay -->|request error| Setup
Replay -->|response| Verdict
Verdict -->|violation or strict warning| Finding
Verdict -->|pass or non-strict warning| Clean
classDef input fill:#161b22,stroke:#58a6ff,color:#f0f6fc,stroke-width:2px;
classDef process fill:#1f2937,stroke:#8b949e,color:#f0f6fc;
classDef decision fill:#221b2e,stroke:#d2a8ff,color:#f0f6fc,stroke-width:2px;
classDef failure fill:#3d1519,stroke:#f85149,color:#ff7b72,stroke-width:2px;
classDef success fill:#102a18,stroke:#3fb950,color:#56d364,stroke-width:2px;
class Contract input;
class Matrix,Safety,Replay process;
class Verdict decision;
class Setup,Finding failure;
class Clean success;
What AuthzTrace does
AuthzTrace is an authorization contract test runner for REST APIs. You describe test identities, object ownership, and expected access once. AuthzTrace expands every endpoint across each owned object and declared actor, including anonymous actors you explicitly define.
GET /invoices/inv_A -> 200means nothing by itself. When the contract saysinv_Abelongs to Alice, the same200for Bob is a proven BOLA.
| You declare | AuthzTrace generates | CI receives |
|---|---|---|
| Actors and credentials | Every endpoint x object x declared actor request | A reproducible authorization verdict |
| Owners and fixture IDs | Owner, cross-user, and declared anonymous checks | SARIF findings with stable fingerprints |
| Endpoints and access rules | Status and response-leak assertions | Exit codes that separate findings from broken setup |
Quickstart
Install the CLI and scaffold a contract from an OpenAPI document:
pip install authztrace
authztrace init --from openapi.yaml
The OpenAPI command is a starting point, not authorization inference. It scaffolds single-object routes with one path parameter, or query parameters named id / object_id; review the result and add unsupported or nested routes manually.
Point base_url at a running non-production API, then add stable test-object IDs and actor credentials. Secrets can stay in environment variables:
export ALICE_TOKEN="..."
export BOB_TOKEN="..."
authztrace run -c authztrace.yaml --sarif authztrace.sarif
No OpenAPI document? Start from the working example.
Run it in GitHub Actions
permissions:
contents: read
actions: read
security-events: write
steps:
- uses: actions/checkout@v4
# Start your API here, or point base_url at a reachable test environment.
- uses: Asttr0/AuthzTrace@v0.4.0
env:
ALICE_TOKEN: ${{ secrets.ALICE_TOKEN }}
BOB_TOKEN: ${{ secrets.BOB_TOKEN }}
with:
config: authztrace.yaml
sarif: authztrace.sarif
- uses: github/codeql-action/upload-sarif@v4
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
with:
sarif_file: authztrace.sarif
The contract
This contract says Alice and Bob each own one invoice. Owners may read their own invoice; every other identity must be denied without receiving the owner's marker.
base_url: https://api.test.example.com
actors:
alice: { auth: { type: bearer, token: "${ALICE_TOKEN}" } }
bob: { auth: { type: bearer, token: "${BOB_TOKEN}" } }
anon: { auth: { type: none } }
resources:
invoice:
ids: { alice: inv_A, bob: inv_B }
markers: { alice: "Alice private", bob: "Bob private" }
endpoints:
- request: GET /api/invoices/{id}
allow: [owner]
assertions:
allow_contains: ["{marker}"]
deny_not_contains: ["{marker}"]
policy:
deny_status: [401, 403, 404]
That single endpoint becomes six checks: one endpoint x two owned objects x three declared actors. Alice and Bob must retrieve their own marker; the other user and anon must receive a deny status and never see it.
Object IDs can also live in query parameters, headers, JSON, or form bodies. Endpoint allow rules accept owner, named actors, authenticated, anonymous, all, or *.
Runtime login flows
Actors can acquire credentials from the API before preflight instead of receiving a static token. Each actor gets an isolated HTTP session, and a failed login or missing credential aborts the run as untrustworthy setup with exit code 2.
actors:
alice:
auth:
type: login
request: POST /api/login
json:
username: alice
password: "${ALICE_PASSWORD}"
extract: { from: json, path: session.access_token }
credential: { type: bearer }
extract.from accepts json, header, or cookie. JSON extraction uses a dotted path; header and cookie extraction use name. The resulting credential can be applied as bearer, header, or cookie, and expect_status can override the default 2xx login expectation. OAuth-style form payloads, separate HTTP(S) identity-provider URLs, redirect control, and custom token schemes are supported.
Login requests are explicit setup operations and therefore run before the read-only endpoint safety gate, including POST logins. Keep targets pointed at controlled non-production environments. See the authentication guide and complete login-flow demo contract.
Built for trustworthy CI
| Behavior | Guarantee |
|---|---|
| Credential preflight | Every executable allow row must pass before deny rows run. Broken credentials or fixtures cannot produce a false green. |
| Read-only default | Only GET, HEAD, and OPTIONS execute automatically. Other methods are visibly skipped unless marked safe: true or enabled with --include-unsafe. |
| Leak detection | A denied response still fails if it contains a forbidden marker or JSON field. |
| CI-native reports | Terminal, SARIF, JSON, and JUnit output; SARIF includes stable fingerprints for GitHub code scanning. |
| Flexible authentication | Static Bearer, custom-header, cookie, and Basic credentials; anonymous actors; and isolated request-and-extract login flows. Actor credentials are excluded from reports. |
| Exit | Meaning |
|---|---|
0 |
No failing findings among executed checks; warnings and skipped unsafe rows remain visible |
1 |
BOLA, response leak, or strict warning |
2 |
Untrustworthy setup: bad credentials, unreadable owner fixture, invalid contract, or unreachable API |
Current scope
AuthzTrace is alpha software focused on REST authorization regression testing with stable fixtures and static or runtime login credentials. Next priorities are nested parent/child ownership and GraphQL BOLA coverage. See the authorization test corpus for supported and planned cases.
Found AuthzTrace useful? Star the repository so more API teams can find it.
MIT © 2026 Mohamed Taha Slimani · @Asttr0 · Issues
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 authztrace-0.4.0.tar.gz.
File metadata
- Download URL: authztrace-0.4.0.tar.gz
- Upload date:
- Size: 29.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2be1bcbfef3a27b22a8c320541ed5ae907e22741006693ed4c23f13135aef12
|
|
| MD5 |
deb292f24b23a8338686f0f4f4953a2d
|
|
| BLAKE2b-256 |
2ac130abe87b0045a46a0886da96ba38d556861eb53d1cd83cb4b736b48b9013
|
Provenance
The following attestation bundles were made for authztrace-0.4.0.tar.gz:
Publisher:
release.yml on Asttr0/AuthzTrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
authztrace-0.4.0.tar.gz -
Subject digest:
f2be1bcbfef3a27b22a8c320541ed5ae907e22741006693ed4c23f13135aef12 - Sigstore transparency entry: 2140398498
- Sigstore integration time:
-
Permalink:
Asttr0/AuthzTrace@c97e148e8830fde76e7bb3e668383b5fd50de213 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Asttr0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c97e148e8830fde76e7bb3e668383b5fd50de213 -
Trigger Event:
push
-
Statement type:
File details
Details for the file authztrace-0.4.0-py3-none-any.whl.
File metadata
- Download URL: authztrace-0.4.0-py3-none-any.whl
- Upload date:
- Size: 23.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c701e738744a4151f992b22fd65919ad44360195550f0494f6dc1aed6f32c9a
|
|
| MD5 |
a9fc3e4c20771a11f2d7c917a7998316
|
|
| BLAKE2b-256 |
941f9b7c7185921ff2f65a67a94c961554c3156df88a548c5b6d2ce498f98d25
|
Provenance
The following attestation bundles were made for authztrace-0.4.0-py3-none-any.whl:
Publisher:
release.yml on Asttr0/AuthzTrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
authztrace-0.4.0-py3-none-any.whl -
Subject digest:
2c701e738744a4151f992b22fd65919ad44360195550f0494f6dc1aed6f32c9a - Sigstore transparency entry: 2140398504
- Sigstore integration time:
-
Permalink:
Asttr0/AuthzTrace@c97e148e8830fde76e7bb3e668383b5fd50de213 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Asttr0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c97e148e8830fde76e7bb3e668383b5fd50de213 -
Trigger Event:
push
-
Statement type: