granttrace 2.4.0__tar.gz → 2.4.1__tar.gz
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.
- {granttrace-2.4.0 → granttrace-2.4.1}/CHANGELOG.md +45 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/CONTRIBUTING.md +1 -1
- {granttrace-2.4.0 → granttrace-2.4.1}/PKG-INFO +38 -17
- {granttrace-2.4.0 → granttrace-2.4.1}/README.md +37 -16
- {granttrace-2.4.0 → granttrace-2.4.1}/VERIFICATION.md +29 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/api_sentinel.py +33 -4
- {granttrace-2.4.0 → granttrace-2.4.1}/core/__init__.py +1 -1
- {granttrace-2.4.0 → granttrace-2.4.1}/core/auditor.py +7 -6
- {granttrace-2.4.0 → granttrace-2.4.1}/core/config_validator.py +4 -1
- granttrace-2.4.1/core/demo.py +228 -0
- granttrace-2.4.1/core/demo_assets/__init__.py +1 -0
- granttrace-2.4.1/core/demo_assets/demo-config.json +82 -0
- granttrace-2.4.1/core/demo_assets/mock-data.json +40 -0
- granttrace-2.4.1/core/demo_assets/openapi.json +278 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/evidence.py +12 -12
- {granttrace-2.4.0 → granttrace-2.4.1}/core/generator.py +3 -3
- {granttrace-2.4.0 → granttrace-2.4.1}/core/parameters.py +2 -2
- {granttrace-2.4.0 → granttrace-2.4.1}/core/parser.py +8 -8
- granttrace-2.4.1/docs/release-checklist.md +105 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/examples/README.md +5 -3
- {granttrace-2.4.0 → granttrace-2.4.1}/examples/sample_report.html +90 -90
- {granttrace-2.4.0 → granttrace-2.4.1}/examples/sample_result.json +94 -94
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/PKG-INFO +38 -17
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/SOURCES.txt +7 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/pyproject.toml +6 -2
- {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_install.py +66 -0
- granttrace-2.4.1/tests/test_demo.py +501 -0
- granttrace-2.4.1/tests/test_first_run_guidance.py +162 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_rc2_security_hardening.py +1 -1
- granttrace-2.4.0/docs/release-checklist.md +0 -74
- {granttrace-2.4.0 → granttrace-2.4.1}/LICENSE +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/MANIFEST.in +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/SECURITY.md +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/config.example.json +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/config.schema.json +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/config_scaffold.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/diff.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/models.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/reporter.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/core/transactions.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/docs/advanced.md +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/docs/assets/report-preview.png +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/docs/business-scenarios.md +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/docs/configuration.md +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/docs/parameter-serialization.md +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/dependency_links.txt +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/entry_points.txt +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/requires.txt +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/top_level.txt +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/mock_server/__init__.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/mock_server/server.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/openapi.json +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_business_scenarios.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_examples.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/setup.cfg +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/test_suite.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_auditor_integration.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_business_authorization.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_config_scaffold.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_config_validation.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_diff.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_diff_hardening.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_evidence.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_parameter_serialization.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_parser_generator_hardening.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_rc3_hardening.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_bola.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_config.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_evidence.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_identity.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_transactions.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_report_usability.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_schema_regressions.py +0 -0
- {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_v22_auditor_hardening.py +0 -0
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.4.1 - 2026-10-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add `granttrace demo` with packaged OpenAPI, configuration and fictional mock
|
|
8
|
+
data, so an installed wheel can demonstrate identity comparison, BOLA / IDOR,
|
|
9
|
+
Mass Assignment, independent readback and rollback verification outside a Git
|
|
10
|
+
checkout without user credentials or external API access.
|
|
11
|
+
- Start the disposable mock on a dynamic loopback port, close it on completion
|
|
12
|
+
or failure, and retain HTML / JSON reports in a unique output directory.
|
|
13
|
+
- Add `--output-dir` and optional `--read-only` controls for the built-in demo.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Put the single-command installed-package demo before the source-checkout
|
|
18
|
+
walkthrough and retain configuration onboarding and safety boundaries. During
|
|
19
|
+
release preparation, distinguish the upcoming package from the published stable
|
|
20
|
+
version and do not claim an unpublished PyPI release is available.
|
|
21
|
+
- Give missing spec/config files and offline configuration failures a concise
|
|
22
|
+
next step; an empty write allowlist still sends no PATCH requests.
|
|
23
|
+
- Promote the 2.4.1.dev0 development package to 2.4.1 consistently across runtime,
|
|
24
|
+
package metadata and generated reports; update the release acceptance checklist.
|
|
25
|
+
|
|
26
|
+
### Quality
|
|
27
|
+
|
|
28
|
+
- Resolve the existing 20 mypy errors in six files using accurate annotations and
|
|
29
|
+
type narrowing, without new suppression directives or weaker mypy settings.
|
|
30
|
+
- Make mypy a blocking CI check and add a minimum 80% branch-aware coverage gate.
|
|
31
|
+
- Add weekly Dependabot checks for pip dependencies and GitHub Actions, without
|
|
32
|
+
automatic merging; exclude the release workflow from dependency updates.
|
|
33
|
+
- Verify installed demo resources, source-independent startup, both demo modes,
|
|
34
|
+
complete database restoration and local server shutdown.
|
|
35
|
+
|
|
36
|
+
### Security / Safety
|
|
37
|
+
|
|
38
|
+
- The demo accepts no external target or user credentials. Its active PATCH checks
|
|
39
|
+
run only against bundled, disposable data on a dynamic `127.0.0.1` server.
|
|
40
|
+
- Keep auditing users' own APIs read-only by default; `granttrace demo --read-only`
|
|
41
|
+
sends no PATCH requests.
|
|
42
|
+
- Preserve the BOLA / IDOR and Mass Assignment detection criteria, explicit write
|
|
43
|
+
allowlists, independent GET readback, original-state snapshots and rollback
|
|
44
|
+
verification. Failed recovery still blocks subsequent writes.
|
|
45
|
+
- Keep the existing `granttrace --spec ...` CLI, TLS / HTTP / redirect / proxy
|
|
46
|
+
safety boundaries and evidence minimisation unchanged.
|
|
47
|
+
|
|
3
48
|
## 2.4.0 - 2026-10-03
|
|
4
49
|
|
|
5
50
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: granttrace
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Safety-first OpenAPI authorization auditing tool for BOLA, IDOR and Mass Assignment testing
|
|
5
5
|
Author: GrantTrace contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -32,6 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|

|
|
33
33
|
|
|
34
34
|
**Stable:** [v2.4.0](https://github.com/ysc070528/granttrace/releases/tag/v2.4.0)
|
|
35
|
+
**Release prep:** v2.4.1 (not yet released)
|
|
35
36
|
|
|
36
37
|
An OpenAPI authorization auditor that compares identities, verifies persisted changes, and reports restoration evidence.
|
|
37
38
|
|
|
@@ -49,30 +50,23 @@ An OpenAPI authorization auditor that compares identities, verifies persisted ch
|
|
|
49
50
|
|
|
50
51
|
## 五分钟体验
|
|
51
52
|
|
|
52
|
-
需要 Python 3.9
|
|
53
|
+
需要 Python 3.9+。**v2.4.1 正在准备发布,尚未发布到 PyPI**;当前稳定版 v2.4.0 暂不支持 `demo`。v2.4.1 正式发布到 PyPI 后,首次体验只需:
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
python -m pip install -e .
|
|
58
|
-
python mock_server/server.py
|
|
56
|
+
pip install granttrace
|
|
57
|
+
granttrace demo
|
|
59
58
|
```
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
审查当前发布准备分支时,先安装本轮构建的 wheel,再运行同一条 demo 命令:
|
|
62
61
|
|
|
63
62
|
```bash
|
|
64
|
-
|
|
63
|
+
python -m pip install dist/granttrace-2.4.1-py3-none-any.whl
|
|
64
|
+
granttrace demo
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
在这个可丢弃的本地靶场体验写测试和恢复核验:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --allow-write-tests --export-json result.local.json
|
|
73
|
-
```
|
|
67
|
+
安装后可在任意目录运行,无需 clone、准备配置、另开终端或访问外部 API。Demo 自动启动动态端口的 `127.0.0.1` 靶场,比较身份访问、演示 BOLA / IDOR 与 Mass Assignment,并通过独立读回与恢复核验展示 PATCH 测试证据。**主动写操作仅发生在内置、可丢弃的本机靶场**;demo 不接受外部 target 或用户凭据,结束时关闭靶场。
|
|
74
68
|
|
|
75
|
-
|
|
69
|
+
HTML / JSON 报告保留在当前目录的 `granttrace-demo/` 唯一子目录,终端会输出绝对路径,不覆盖已有报告。可选用 `granttrace demo --output-dir PATH` 指定报告父目录,或 `granttrace demo --read-only` 仅体验只读身份访问。
|
|
76
70
|
|
|
77
71
|
完整离线示例:[HTML 报告](examples/sample_report.html)(下载后用浏览器打开)与 [JSON 结果](examples/sample_result.json)。
|
|
78
72
|
|
|
@@ -91,7 +85,34 @@ granttrace --spec your-openapi.yaml --config config.local.json --validate-config
|
|
|
91
85
|
granttrace --spec your-openapi.yaml --config config.local.json --dry-run --export-json plan.local.json
|
|
92
86
|
```
|
|
93
87
|
|
|
94
|
-
|
|
88
|
+
接入自己的 API 时仍默认只读,主动 PATCH 测试必须显式开启并满足 allowlist、独立 readback 和恢复条件。账户、API Key / Cookie、合法共享和管理员访问配置见 [配置指南](docs/configuration.md)。支持的数组、布尔值及 `style` / `explode` 编码见 [参数规则](docs/parameter-serialization.md)。接入实测前请核对 [安全边界](SECURITY.md)。
|
|
89
|
+
|
|
90
|
+
## 从源码开发 / 运行仓库 mock
|
|
91
|
+
|
|
92
|
+
需要查看源码或手工运行仓库靶场时:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone https://github.com/ysc070528/granttrace.git
|
|
96
|
+
cd granttrace
|
|
97
|
+
python -m pip install -e .
|
|
98
|
+
python mock_server/server.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
另开终端,进入同一目录执行首次只读扫描:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --export-json result.local.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
用浏览器打开默认生成的 **`granttrace_report.html`**:先查看发现与修复建议,再按状态筛选端点、展开证据。预期结果为 **1 个 CONFIRMED、1 个 PUBLIC、1 个 SECURE、2 个 SKIPPED**,确定性覆盖率 **60%**。两个 PATCH 因默认只读而跳过。JSON 结果写入 `result.local.json`;两个本地产物均由 `.gitignore` 忽略。
|
|
108
|
+
|
|
109
|
+
在这个可丢弃的本地靶场体验写测试和恢复核验:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --allow-write-tests --export-json result.local.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
再次打开 **`granttrace_report.html`**。预期 **2 个 CONFIRMED、1 个 PUBLIC、2 个 SECURE**,确定性覆盖率 **100%**,批量赋值发现的 `rollback_verified` 为 `true`。本次扫描更新同名 HTML / JSON 文件;这些结果只属于内置五个演示端点。
|
|
95
116
|
|
|
96
117
|
## 验证与边界
|
|
97
118
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
**Stable:** [v2.4.0](https://github.com/ysc070528/granttrace/releases/tag/v2.4.0)
|
|
8
|
+
**Release prep:** v2.4.1 (not yet released)
|
|
8
9
|
|
|
9
10
|
An OpenAPI authorization auditor that compares identities, verifies persisted changes, and reports restoration evidence.
|
|
10
11
|
|
|
@@ -22,30 +23,23 @@ An OpenAPI authorization auditor that compares identities, verifies persisted ch
|
|
|
22
23
|
|
|
23
24
|
## 五分钟体验
|
|
24
25
|
|
|
25
|
-
需要 Python 3.9
|
|
26
|
+
需要 Python 3.9+。**v2.4.1 正在准备发布,尚未发布到 PyPI**;当前稳定版 v2.4.0 暂不支持 `demo`。v2.4.1 正式发布到 PyPI 后,首次体验只需:
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
python -m pip install -e .
|
|
31
|
-
python mock_server/server.py
|
|
29
|
+
pip install granttrace
|
|
30
|
+
granttrace demo
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
审查当前发布准备分支时,先安装本轮构建的 wheel,再运行同一条 demo 命令:
|
|
35
34
|
|
|
36
35
|
```bash
|
|
37
|
-
|
|
36
|
+
python -m pip install dist/granttrace-2.4.1-py3-none-any.whl
|
|
37
|
+
granttrace demo
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
在这个可丢弃的本地靶场体验写测试和恢复核验:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --allow-write-tests --export-json result.local.json
|
|
46
|
-
```
|
|
40
|
+
安装后可在任意目录运行,无需 clone、准备配置、另开终端或访问外部 API。Demo 自动启动动态端口的 `127.0.0.1` 靶场,比较身份访问、演示 BOLA / IDOR 与 Mass Assignment,并通过独立读回与恢复核验展示 PATCH 测试证据。**主动写操作仅发生在内置、可丢弃的本机靶场**;demo 不接受外部 target 或用户凭据,结束时关闭靶场。
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
HTML / JSON 报告保留在当前目录的 `granttrace-demo/` 唯一子目录,终端会输出绝对路径,不覆盖已有报告。可选用 `granttrace demo --output-dir PATH` 指定报告父目录,或 `granttrace demo --read-only` 仅体验只读身份访问。
|
|
49
43
|
|
|
50
44
|
完整离线示例:[HTML 报告](examples/sample_report.html)(下载后用浏览器打开)与 [JSON 结果](examples/sample_result.json)。
|
|
51
45
|
|
|
@@ -64,7 +58,34 @@ granttrace --spec your-openapi.yaml --config config.local.json --validate-config
|
|
|
64
58
|
granttrace --spec your-openapi.yaml --config config.local.json --dry-run --export-json plan.local.json
|
|
65
59
|
```
|
|
66
60
|
|
|
67
|
-
|
|
61
|
+
接入自己的 API 时仍默认只读,主动 PATCH 测试必须显式开启并满足 allowlist、独立 readback 和恢复条件。账户、API Key / Cookie、合法共享和管理员访问配置见 [配置指南](docs/configuration.md)。支持的数组、布尔值及 `style` / `explode` 编码见 [参数规则](docs/parameter-serialization.md)。接入实测前请核对 [安全边界](SECURITY.md)。
|
|
62
|
+
|
|
63
|
+
## 从源码开发 / 运行仓库 mock
|
|
64
|
+
|
|
65
|
+
需要查看源码或手工运行仓库靶场时:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/ysc070528/granttrace.git
|
|
69
|
+
cd granttrace
|
|
70
|
+
python -m pip install -e .
|
|
71
|
+
python mock_server/server.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
另开终端,进入同一目录执行首次只读扫描:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --export-json result.local.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
用浏览器打开默认生成的 **`granttrace_report.html`**:先查看发现与修复建议,再按状态筛选端点、展开证据。预期结果为 **1 个 CONFIRMED、1 个 PUBLIC、1 个 SECURE、2 个 SKIPPED**,确定性覆盖率 **60%**。两个 PATCH 因默认只读而跳过。JSON 结果写入 `result.local.json`;两个本地产物均由 `.gitignore` 忽略。
|
|
81
|
+
|
|
82
|
+
在这个可丢弃的本地靶场体验写测试和恢复核验:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --allow-write-tests --export-json result.local.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
再次打开 **`granttrace_report.html`**。预期 **2 个 CONFIRMED、1 个 PUBLIC、2 个 SECURE**,确定性覆盖率 **100%**,批量赋值发现的 `rollback_verified` 为 `true`。本次扫描更新同名 HTML / JSON 文件;这些结果只属于内置五个演示端点。
|
|
68
89
|
|
|
69
90
|
## 验证与边界
|
|
70
91
|
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# GrantTrace 验收记录
|
|
2
2
|
|
|
3
|
+
## 2026-10-03 2.4.1.dev0 内置 demo 验收
|
|
4
|
+
|
|
5
|
+
本轮仅改善安装后的首次体验,稳定版仍为 v2.4.0,开发版本为
|
|
6
|
+
2.4.1.dev0;本记录不表示开发版已发布。原检测语义、配置验证规则和
|
|
7
|
+
PATCH allowlist / 独立读回 / 回滚限制保持不变,兼容入口保留。
|
|
8
|
+
|
|
9
|
+
本地 Windows / Python 3.14.5 实际验证结果:
|
|
10
|
+
|
|
11
|
+
- 完整 unittest 与 coverage 运行均为 **304 项通过**,0 失败、0 错误、
|
|
12
|
+
0 跳过,其中新增 27 项 demo 测试、9 项首次使用提示测试。
|
|
13
|
+
- 总覆盖率 **80.85%**,语句 **83.78%**,分支 **75.17%**;分别高于
|
|
14
|
+
之前的 79.66% / 82.66% / 74.02%。
|
|
15
|
+
- 业务场景、JSON / YAML 示例、真实 wheel 安装脚本全部通过;Ruff、
|
|
16
|
+
pip check 与严格 pip-audit 通过,未发现已知运行依赖漏洞。
|
|
17
|
+
- mypy 仍为 **20 个问题 / 6 个文件**,新增 demo 未增加类型诊断;保持 advisory。
|
|
18
|
+
- 全新虚拟环境在仓库外的空目录安装 wheel,未复制任何仓库文件,
|
|
19
|
+
`granttrace demo` 成功;环境代理被绕过,靶场只绑定动态 127.0.0.1 端口。
|
|
20
|
+
- 完整 demo 为 **1 BOLA / IDOR + 1 Mass Assignment**;独立 GET 读回、
|
|
21
|
+
`rollback_verified`、整库恢复与服务器关闭均成功。只读 demo 未发送 PATCH。
|
|
22
|
+
- HTML / JSON 报告保留在唯一 run 子目录,不覆盖旧文件;本地安装验收
|
|
23
|
+
证据另存于被忽略的 `dist/demo-validation/`。
|
|
24
|
+
- 异常回归包含构造/启动/ready 失败、JSON 与报告错误、真实 PATCH 后
|
|
25
|
+
KeyboardInterrupt、读回失败后恢复、回滚证据缺失与整库残留;均可靠关闭
|
|
26
|
+
靶场,恢复失败或证据不足不会显示 demo 成功。
|
|
27
|
+
|
|
28
|
+
Python 3.9 / 3.12 / 3.14 与 CodeQL 的远程结果以本轮 PR 的实际检查为准。
|
|
29
|
+
本轮只创建 PR 供审查,不合并、不创建 tag、不发布 GitHub Release 或 PyPI。
|
|
30
|
+
以下历史记录完整保留。
|
|
31
|
+
|
|
3
32
|
## 2026-10-03 v2.4.0 发布前验收
|
|
4
33
|
|
|
5
34
|
本轮在已合并 PR #11 的最新 `main`(`4b65e54`)基础上创建
|
|
@@ -33,7 +33,8 @@ BANNER = rf"""
|
|
|
33
33
|
|
|
34
34
|
def build_argument_parser() -> argparse.ArgumentParser:
|
|
35
35
|
parser = argparse.ArgumentParser(
|
|
36
|
-
description="Safety-first OpenAPI authorization and mass-assignment auditor"
|
|
36
|
+
description="Safety-first OpenAPI authorization and mass-assignment auditor",
|
|
37
|
+
epilog="Built-in local demo: granttrace demo (no spec, config or credentials needed)",
|
|
37
38
|
)
|
|
38
39
|
parser.add_argument("--spec", "-s", default=None,
|
|
39
40
|
help="OpenAPI/Swagger JSON or YAML (default: openapi.json)")
|
|
@@ -128,10 +129,26 @@ def _reject_constant(val: str) -> None:
|
|
|
128
129
|
raise ValueError(f"Non-standard JSON number not allowed: {val}")
|
|
129
130
|
|
|
130
131
|
|
|
132
|
+
_MISSING_FILE_GUIDANCE = (
|
|
133
|
+
"For the built-in local demo, run 'granttrace demo'. "
|
|
134
|
+
"For your own API, select a local OpenAPI file and create a draft with "
|
|
135
|
+
"'granttrace --spec <openapi-file> --init-config config.local.json'."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _print_config_next_steps() -> None:
|
|
140
|
+
print("[NEXT] Review identities, resource IDs in parameter_values, and independent GET readback mappings. "
|
|
141
|
+
"Access expectations must come from your API's business rules.", file=sys.stderr)
|
|
142
|
+
print("[NEXT] After editing, run --validate-config, then --dry-run with the same --spec and --config. "
|
|
143
|
+
"Both checks are offline.", file=sys.stderr)
|
|
144
|
+
|
|
145
|
+
|
|
131
146
|
def _load_spec(path: str) -> Dict[str, Any]:
|
|
132
147
|
"""Use the scanner's local JSON/YAML loader for both preflight modes."""
|
|
133
148
|
try:
|
|
134
149
|
return OpenAPIParser(path).raw_spec
|
|
150
|
+
except FileNotFoundError as exc:
|
|
151
|
+
raise ValueError(f"Cannot load specification '{path}': {exc}. {_MISSING_FILE_GUIDANCE}") from exc
|
|
135
152
|
except Exception as exc:
|
|
136
153
|
raise ValueError(f"Cannot load specification '{path}': {exc}") from exc
|
|
137
154
|
|
|
@@ -147,9 +164,10 @@ def _reject_incomplete_draft(config: Any) -> None:
|
|
|
147
164
|
|
|
148
165
|
def run_config_validation(config_path_str: str, spec_path: Optional[str] = None) -> int:
|
|
149
166
|
config_file = Path(config_path_str)
|
|
150
|
-
safe_path_str = sanitize_log_text(config_path_str)
|
|
167
|
+
safe_path_str = sanitize_log_text(sanitize_text(config_path_str))
|
|
151
168
|
if not config_file.is_file():
|
|
152
169
|
print(f"[ERROR] Configuration file does not exist: {safe_path_str}", file=sys.stderr)
|
|
170
|
+
print("[NEXT] " + _MISSING_FILE_GUIDANCE, file=sys.stderr)
|
|
153
171
|
return 2
|
|
154
172
|
try:
|
|
155
173
|
with config_file.open("r", encoding="utf-8") as handle:
|
|
@@ -202,6 +220,7 @@ def run_config_validation(config_path_str: str, spec_path: Optional[str] = None)
|
|
|
202
220
|
print(f"[WARN] In addition, {result.summary.warnings_count} warning(s) observed:", file=sys.stderr)
|
|
203
221
|
for warning in result.warnings:
|
|
204
222
|
print(warning.format(), file=sys.stderr)
|
|
223
|
+
_print_config_next_steps()
|
|
205
224
|
return 2
|
|
206
225
|
|
|
207
226
|
print(f"[OK] Configuration is valid: {safe_path_str}")
|
|
@@ -221,7 +240,7 @@ def _load_config(path: Optional[str]) -> Dict[str, Any]:
|
|
|
221
240
|
return {}
|
|
222
241
|
config_path = Path(path)
|
|
223
242
|
if not config_path.is_file():
|
|
224
|
-
raise ValueError(f"configuration file does not exist: {path}")
|
|
243
|
+
raise ValueError(f"configuration file does not exist: {path}. {_MISSING_FILE_GUIDANCE}")
|
|
225
244
|
try:
|
|
226
245
|
with config_path.open("r", encoding="utf-8") as handle:
|
|
227
246
|
config = json.load(handle, parse_constant=_reject_constant)
|
|
@@ -241,7 +260,11 @@ def _ensure_parent(path: str) -> None:
|
|
|
241
260
|
|
|
242
261
|
|
|
243
262
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
244
|
-
|
|
263
|
+
selected = list(sys.argv[1:] if argv is None else argv)
|
|
264
|
+
if selected and selected[0] == "demo":
|
|
265
|
+
from core.demo import demo_main
|
|
266
|
+
return demo_main(selected[1:])
|
|
267
|
+
args = build_argument_parser().parse_args(selected)
|
|
245
268
|
print(BANNER)
|
|
246
269
|
if args.init_config is not None:
|
|
247
270
|
if args.config or args.validate_config or args.dry_run or args.allow_write_tests or args.write_endpoint:
|
|
@@ -309,6 +332,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
309
332
|
)
|
|
310
333
|
for issue in val_result.errors:
|
|
311
334
|
print(issue.format(), file=sys.stderr)
|
|
335
|
+
_print_config_next_steps()
|
|
312
336
|
return 2
|
|
313
337
|
|
|
314
338
|
try:
|
|
@@ -317,6 +341,11 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
317
341
|
if not isinstance(write_allowlist, list):
|
|
318
342
|
raise ValueError("write_allowlist must be a list")
|
|
319
343
|
|
|
344
|
+
if args.allow_write_tests and not write_allowlist:
|
|
345
|
+
print("[WARN] Write tests requested, but write_allowlist is empty; no PATCH requests will be sent. "
|
|
346
|
+
"Review explicit PATCH entries and independent GET readbacks, then run --validate-config "
|
|
347
|
+
"and --dry-run before scanning.", file=sys.stderr)
|
|
348
|
+
|
|
320
349
|
auditor = APISentinelAuditor(
|
|
321
350
|
spec_path=spec_path,
|
|
322
351
|
target_base_url=args.target,
|
|
@@ -19,7 +19,7 @@ import urllib.error
|
|
|
19
19
|
import urllib.parse
|
|
20
20
|
import urllib.request
|
|
21
21
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
22
|
-
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
22
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
|
23
23
|
|
|
24
24
|
from core import __version__
|
|
25
25
|
from core.config_validator import ConfigValidator
|
|
@@ -102,7 +102,7 @@ class APISentinelAuditor:
|
|
|
102
102
|
if not is_valid or method != "PATCH":
|
|
103
103
|
raise ValueError("write_allowlist entries must be 'PATCH /case-sensitive-path'")
|
|
104
104
|
self.write_allowlist.add(canonical)
|
|
105
|
-
self._writes_halted_reason = None
|
|
105
|
+
self._writes_halted_reason: Optional[str] = None
|
|
106
106
|
|
|
107
107
|
context = ssl._create_unverified_context() if insecure_ssl else ssl.create_default_context()
|
|
108
108
|
self._opener = urllib.request.build_opener(
|
|
@@ -495,7 +495,7 @@ class APISentinelAuditor:
|
|
|
495
495
|
if visitor_self is not None:
|
|
496
496
|
request_results["visitor_self"] = visitor_self
|
|
497
497
|
transport_errors = [f"{name}: {item.error}" for name, item in request_results.items() if item.error]
|
|
498
|
-
evidence = {
|
|
498
|
+
evidence: Dict[str, Any] = {
|
|
499
499
|
"target_url": owner_url,
|
|
500
500
|
"visitor_self_url": visitor_self_url if visitor_self is not None else None,
|
|
501
501
|
**{name: self._safe_body(item) for name, item in request_results.items()},
|
|
@@ -621,7 +621,7 @@ class APISentinelAuditor:
|
|
|
621
621
|
if last:
|
|
622
622
|
cursor[part] = value
|
|
623
623
|
return
|
|
624
|
-
expected = [] if isinstance(next_part, int) else {}
|
|
624
|
+
expected: Union[List[object], Dict[str, object]] = [] if isinstance(next_part, int) else {}
|
|
625
625
|
if not isinstance(cursor.get(part), type(expected)):
|
|
626
626
|
cursor[part] = expected
|
|
627
627
|
cursor = cursor[part]
|
|
@@ -680,7 +680,8 @@ class APISentinelAuditor:
|
|
|
680
680
|
else {},
|
|
681
681
|
})
|
|
682
682
|
continue
|
|
683
|
-
|
|
683
|
+
configured_payload = item.get("payload")
|
|
684
|
+
payload = configured_payload if isinstance(configured_payload, dict) else item
|
|
684
685
|
for field_path, value in payload.items():
|
|
685
686
|
cases.append({
|
|
686
687
|
"field_path": str(field_path),
|
|
@@ -954,7 +955,7 @@ class APISentinelAuditor:
|
|
|
954
955
|
"recovery_sent": transaction["recovery_sent"],
|
|
955
956
|
})
|
|
956
957
|
if persisted:
|
|
957
|
-
finding = {
|
|
958
|
+
finding: Dict[str, Any] = {
|
|
958
959
|
"type": "Mass Assignment (independent readback confirmed)",
|
|
959
960
|
"cwe": "CWE-915", "severity": "Critical", "endpoint": operation_key,
|
|
960
961
|
"target_url": mutation_url,
|
|
@@ -977,7 +977,10 @@ class ConfigValidator:
|
|
|
977
977
|
expected="JSON object",
|
|
978
978
|
actual=safe_diagnostic_value(parameter.get("schema")),
|
|
979
979
|
))
|
|
980
|
-
if
|
|
980
|
+
if (
|
|
981
|
+
valid_name and isinstance(name, str) and isinstance(location, str)
|
|
982
|
+
and location in ("path", "query")
|
|
983
|
+
):
|
|
981
984
|
identity = (name, location)
|
|
982
985
|
if identity in identities:
|
|
983
986
|
issues.append(ConfigIssue(
|