authztrace 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- authztrace/__init__.py +3 -0
- authztrace/cli.py +130 -0
- authztrace/config.py +224 -0
- authztrace/engine.py +284 -0
- authztrace/matrix.py +81 -0
- authztrace/models.py +98 -0
- authztrace/openapi.py +135 -0
- authztrace/py.typed +1 -0
- authztrace/report.py +246 -0
- authztrace/templating.py +22 -0
- authztrace-0.3.1.dist-info/METADATA +327 -0
- authztrace-0.3.1.dist-info/RECORD +16 -0
- authztrace-0.3.1.dist-info/WHEEL +5 -0
- authztrace-0.3.1.dist-info/entry_points.txt +2 -0
- authztrace-0.3.1.dist-info/licenses/LICENSE +21 -0
- authztrace-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: authztrace
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Authorization contract testing for IDOR/BOLA — prove user A can't touch user B's objects, in CI.
|
|
5
|
+
Author: Mohamed Taha Slimani (@Asttr0)
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Asttr0/authztrace
|
|
8
|
+
Project-URL: Issues, https://github.com/Asttr0/authztrace/issues
|
|
9
|
+
Keywords: security,idor,bola,api-security,authorization,owasp,appsec,ci,dast
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: requests>=2.28
|
|
18
|
+
Requires-Dist: PyYAML>=6.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# AuthzTrace
|
|
26
|
+
|
|
27
|
+
**Authorization tests for IDOR/BOLA bugs.**
|
|
28
|
+
|
|
29
|
+
AuthzTrace proves that one user cannot access another user's objects by replaying an authorization contract against your real API.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
Most scanners cannot reliably find IDOR/BOLA because they do not know ownership. They can see `/api/invoices/inv_123`, but they do not know that `inv_123` belongs to Alice and must not be readable by Bob.
|
|
37
|
+
|
|
38
|
+
AuthzTrace makes that missing context explicit:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Alice owns inv_alice_001
|
|
42
|
+
Bob owns inv_bob_002
|
|
43
|
+
|
|
44
|
+
AuthzTrace checks:
|
|
45
|
+
- Alice can read Alice's invoice
|
|
46
|
+
- Bob cannot read Alice's invoice
|
|
47
|
+
- Anonymous users cannot read either invoice
|
|
48
|
+
- The same policy holds for path IDs, query IDs, JSON body IDs, and more
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
flowchart LR
|
|
53
|
+
A[Ownership contract] --> B[Generated actor x object matrix]
|
|
54
|
+
B --> C[Preflight setup checks]
|
|
55
|
+
C --> D[Authorization attack rows]
|
|
56
|
+
D --> E[SARIF, JSON, JUnit, terminal]
|
|
57
|
+
E --> F[Fail the PR on real BOLA]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Why It Exists
|
|
61
|
+
|
|
62
|
+
Broken Object Level Authorization is the classic IDOR problem: changing an object ID lets one user reach another user's data. It is still hard to catch automatically because authorization is business logic.
|
|
63
|
+
|
|
64
|
+
AuthzTrace is not a crawler. It is closer to a contract test:
|
|
65
|
+
|
|
66
|
+
| You declare | AuthzTrace proves |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Actors and auth tokens | Each identity can authenticate |
|
|
69
|
+
| Object IDs and owners | Owners can access their own objects |
|
|
70
|
+
| API endpoints | Other users are denied |
|
|
71
|
+
| Response markers | Denied responses do not leak protected data |
|
|
72
|
+
|
|
73
|
+
If credentials or test fixtures are broken, AuthzTrace exits with a setup error instead of giving a false green result.
|
|
74
|
+
|
|
75
|
+
## Quick Demo
|
|
76
|
+
|
|
77
|
+
Install AuthzTrace:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install authztrace
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For local development from this repo:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/Asttr0/AuthzTrace.git
|
|
87
|
+
cd AuthzTrace
|
|
88
|
+
pip install -e .
|
|
89
|
+
pip install -r examples/vulnerable-api/requirements.txt
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Start the deliberately vulnerable demo API:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python examples/vulnerable-api/app.py
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
In another terminal, run AuthzTrace:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export ALICE_TOKEN=alice-token
|
|
102
|
+
export BOB_TOKEN=bob-token
|
|
103
|
+
authztrace run -c examples/authztrace.yaml
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You get a real authorization finding:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
RESULT ACTOR TARGET EXPECT STATUS METHOD PATH
|
|
110
|
+
------------------------------------------------------------------------------------------------
|
|
111
|
+
PASS alice alice allow 200 GET /api/invoices/inv_alice_001
|
|
112
|
+
FAIL bob alice deny 200 GET /api/invoices/inv_alice_001
|
|
113
|
+
-> BOLA: 'bob' accessed alice's invoice (inv_alice_001) - HTTP 200
|
|
114
|
+
PASS anon alice deny 401 GET /api/invoices/inv_alice_001
|
|
115
|
+
...
|
|
116
|
+
SKIP alice alice allow - DELETE /api/invoices/inv_alice_001
|
|
117
|
+
-> unsafe DELETE skipped in read-only mode
|
|
118
|
+
...
|
|
119
|
+
12 passed, 6 failed, 0 warnings, 0 errors, 6 skipped, 24 checks
|
|
120
|
+
categories: bola=6, unsafe_skipped=6
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Stop the first server with `Ctrl+C`, then run the fixed API:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
SECURE=1 python examples/vulnerable-api/app.py
|
|
127
|
+
authztrace run -c examples/authztrace.yaml
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The same contract becomes green:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
18 passed, 0 failed, 0 warnings, 0 errors, 6 skipped, 24 checks
|
|
134
|
+
categories: unsafe_skipped=6
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Minimal Contract
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
base_url: http://localhost:3000
|
|
141
|
+
|
|
142
|
+
actors:
|
|
143
|
+
alice: { auth: { type: bearer, token: "${ALICE_TOKEN}" } }
|
|
144
|
+
bob: { auth: { type: bearer, token: "${BOB_TOKEN}" } }
|
|
145
|
+
anon: { auth: { type: none } }
|
|
146
|
+
|
|
147
|
+
resources:
|
|
148
|
+
invoice:
|
|
149
|
+
ids:
|
|
150
|
+
alice: inv_alice_001
|
|
151
|
+
bob: inv_bob_002
|
|
152
|
+
markers:
|
|
153
|
+
alice: "Alice private invoice"
|
|
154
|
+
bob: "Bob private invoice"
|
|
155
|
+
endpoints:
|
|
156
|
+
- name: read invoice
|
|
157
|
+
request: GET /api/invoices/{id}
|
|
158
|
+
assertions:
|
|
159
|
+
allow_contains: ["{marker}"]
|
|
160
|
+
deny_not_contains: ["{marker}"]
|
|
161
|
+
|
|
162
|
+
policy:
|
|
163
|
+
default: owner-only
|
|
164
|
+
deny_status: [401, 403, 404]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
From this, AuthzTrace generates the full matrix:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
alice -> alice invoice should allow
|
|
171
|
+
bob -> alice invoice should deny
|
|
172
|
+
anon -> alice invoice should deny
|
|
173
|
+
alice -> bob invoice should deny
|
|
174
|
+
bob -> bob invoice should allow
|
|
175
|
+
anon -> bob invoice should deny
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Endpoint Shapes
|
|
179
|
+
|
|
180
|
+
Object IDs can live almost anywhere:
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
endpoints:
|
|
184
|
+
- request: GET /api/invoices/{id}
|
|
185
|
+
|
|
186
|
+
- method: GET
|
|
187
|
+
path: /api/invoices
|
|
188
|
+
query:
|
|
189
|
+
id: "{id}"
|
|
190
|
+
|
|
191
|
+
- method: POST
|
|
192
|
+
path: /api/invoices/lookup
|
|
193
|
+
safe: true
|
|
194
|
+
json:
|
|
195
|
+
invoice_id: "{id}"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Exact placeholders preserve their type. If an ID is numeric, `invoice_id: "{id}"` sends a number, not a string.
|
|
199
|
+
|
|
200
|
+
For endpoints that are intentionally shared, override the default owner-only rule:
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
- request: GET /api/admin/invoices/{id}
|
|
204
|
+
allow: [owner, admin]
|
|
205
|
+
|
|
206
|
+
- request: GET /api/team/invoices/{id}
|
|
207
|
+
allow: [authenticated]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Safe By Default
|
|
211
|
+
|
|
212
|
+
AuthzTrace is designed for CI, so it does not execute mutating endpoints by default.
|
|
213
|
+
|
|
214
|
+
| Method | Default |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `GET`, `HEAD`, `OPTIONS` | executed |
|
|
217
|
+
| `POST`, `PUT`, `PATCH`, `DELETE` | skipped |
|
|
218
|
+
|
|
219
|
+
Mark read-like POST endpoints as safe:
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
- method: POST
|
|
223
|
+
path: /api/search
|
|
224
|
+
safe: true
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Run unsafe endpoints only when you have disposable test data:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
authztrace run -c authztrace.yaml --include-unsafe
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Skipped endpoints are reported as `SKIP`, not counted as passes.
|
|
234
|
+
|
|
235
|
+
## CI Usage
|
|
236
|
+
|
|
237
|
+
AuthzTrace returns clear exit codes:
|
|
238
|
+
|
|
239
|
+
| Code | Meaning |
|
|
240
|
+
|---:|---|
|
|
241
|
+
| `0` | clean |
|
|
242
|
+
| `1` | real finding, or warning in `--strict` mode |
|
|
243
|
+
| `2` | setup or operational failure |
|
|
244
|
+
|
|
245
|
+
Generate SARIF for GitHub code scanning:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
authztrace run -c authztrace.yaml --sarif authztrace.sarif
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Or use the composite GitHub Action:
|
|
252
|
+
|
|
253
|
+
```yaml
|
|
254
|
+
- uses: Asttr0/AuthzTrace@v0.3.1
|
|
255
|
+
with:
|
|
256
|
+
config: authztrace.yaml
|
|
257
|
+
sarif: authztrace.sarif
|
|
258
|
+
strict: "true"
|
|
259
|
+
include-unsafe: "false"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
JSON and JUnit are also available:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
authztrace run -c authztrace.yaml \
|
|
266
|
+
--json authztrace.json \
|
|
267
|
+
--junit authztrace.xml
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Generate A Starter Contract
|
|
271
|
+
|
|
272
|
+
You can scaffold a contract from OpenAPI:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
authztrace init --from openapi.yaml --output authztrace.yaml
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The generator is conservative. It detects simple single-object endpoints such as:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
GET /api/invoices/{invoice_id}
|
|
282
|
+
GET /api/invoices?id=...
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
You still review the generated IDs, actors, and ownership before using it in CI.
|
|
286
|
+
|
|
287
|
+
## What It Catches
|
|
288
|
+
|
|
289
|
+
AuthzTrace currently covers:
|
|
290
|
+
|
|
291
|
+
- horizontal IDOR/BOLA
|
|
292
|
+
- anonymous object access
|
|
293
|
+
- object IDs in path, query, headers, JSON body, and form body
|
|
294
|
+
- denied responses that leak protected markers
|
|
295
|
+
- allowed responses that return the wrong body
|
|
296
|
+
- privileged actors through endpoint `allow` rules
|
|
297
|
+
- unsafe endpoint skipping for CI safety
|
|
298
|
+
- setup failures from broken credentials or invalid fixtures
|
|
299
|
+
|
|
300
|
+
The full pattern roadmap lives in [docs/CORPUS.md](docs/CORPUS.md).
|
|
301
|
+
|
|
302
|
+
## Why Not A Normal Scanner?
|
|
303
|
+
|
|
304
|
+
| Capability | Generic scanner | AuthzTrace |
|
|
305
|
+
|---|:---:|:---:|
|
|
306
|
+
| Knows object ownership | no | yes |
|
|
307
|
+
| Tests cross-user access | weak | yes |
|
|
308
|
+
| Runs in CI | sometimes | yes |
|
|
309
|
+
| Produces SARIF | sometimes | yes |
|
|
310
|
+
| Keeps the security rule next to code | no | yes |
|
|
311
|
+
|
|
312
|
+
The point is simple: if your API says Bob can read Alice's invoice, AuthzTrace should make the pull request fail.
|
|
313
|
+
|
|
314
|
+
## Status
|
|
315
|
+
|
|
316
|
+
Alpha. The core engine, contract format, OpenAPI starter generator, terminal/SARIF/JSON/JUnit output, GitHub Action, read-only safety model, and demo API are working end to end.
|
|
317
|
+
|
|
318
|
+
Next priorities:
|
|
319
|
+
|
|
320
|
+
- login-flow auth
|
|
321
|
+
- nested parent-child ownership
|
|
322
|
+
- GraphQL BOLA checks
|
|
323
|
+
- baselines for accepted deviations
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
MIT (c) 2026 Mohamed Taha Slimani ([@Asttr0](https://github.com/Asttr0))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
authztrace/__init__.py,sha256=Wv1eVy_u7GtVpaZzp1P1vp1Ssihrtj9iCMfkmUpWJJc,90
|
|
2
|
+
authztrace/cli.py,sha256=4khkV9mYYN9Ibv6Vhii-_MhFmzkjbMz5BX19XUYl5aA,4499
|
|
3
|
+
authztrace/config.py,sha256=pU6XFZM3YmVB6ZXdDi8edlIOZRV66UP8Y-thU0qt_wU,7753
|
|
4
|
+
authztrace/engine.py,sha256=tQSmAPvQvBlo9vU1XZyzxBqqzHKES92WwjnGUrzQ0uM,8694
|
|
5
|
+
authztrace/matrix.py,sha256=4w0CbkLTOps5o-bSZuR-rXUADumr3tHf5_xETAVAxZI,3279
|
|
6
|
+
authztrace/models.py,sha256=QTEQrRfAwaFYPHVk_XrLPna6s-5bOH88u7ef7mKAks0,2463
|
|
7
|
+
authztrace/openapi.py,sha256=o-DhgcQjVjha1EezwwSihVQ7-lHOCHlsiashJgCmyh0,4915
|
|
8
|
+
authztrace/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
9
|
+
authztrace/report.py,sha256=SeF6tRxaYMqYUXkkCT929YxJ-ZwEOfiV9D4UtzEnTYU,7959
|
|
10
|
+
authztrace/templating.py,sha256=IvR9q45lGdCoYEN8IZdVCWVVd8o2HMfWzhgUNl9ZxP8,847
|
|
11
|
+
authztrace-0.3.1.dist-info/licenses/LICENSE,sha256=2xahpfgaS44vzbHUBqXlH-5ECTEOZz_KLB65uOROi_U,1087
|
|
12
|
+
authztrace-0.3.1.dist-info/METADATA,sha256=PXLPSaWWy_EPEaDGZb60ZtEYUEmYNnP83tflnW9P1T0,8785
|
|
13
|
+
authztrace-0.3.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
14
|
+
authztrace-0.3.1.dist-info/entry_points.txt,sha256=8OyagE-k25r56BISYZGc38mdjY0eNs_vXwavYAscabo,51
|
|
15
|
+
authztrace-0.3.1.dist-info/top_level.txt,sha256=qCNC_hOV7eCi1-nUZ2YbduCrc_-IS4ShqhZf-3jeiq0,11
|
|
16
|
+
authztrace-0.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohamed Taha Slimani (@Asttr0)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
authztrace
|