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.
Files changed (74) hide show
  1. {granttrace-2.4.0 → granttrace-2.4.1}/CHANGELOG.md +45 -0
  2. {granttrace-2.4.0 → granttrace-2.4.1}/CONTRIBUTING.md +1 -1
  3. {granttrace-2.4.0 → granttrace-2.4.1}/PKG-INFO +38 -17
  4. {granttrace-2.4.0 → granttrace-2.4.1}/README.md +37 -16
  5. {granttrace-2.4.0 → granttrace-2.4.1}/VERIFICATION.md +29 -0
  6. {granttrace-2.4.0 → granttrace-2.4.1}/api_sentinel.py +33 -4
  7. {granttrace-2.4.0 → granttrace-2.4.1}/core/__init__.py +1 -1
  8. {granttrace-2.4.0 → granttrace-2.4.1}/core/auditor.py +7 -6
  9. {granttrace-2.4.0 → granttrace-2.4.1}/core/config_validator.py +4 -1
  10. granttrace-2.4.1/core/demo.py +228 -0
  11. granttrace-2.4.1/core/demo_assets/__init__.py +1 -0
  12. granttrace-2.4.1/core/demo_assets/demo-config.json +82 -0
  13. granttrace-2.4.1/core/demo_assets/mock-data.json +40 -0
  14. granttrace-2.4.1/core/demo_assets/openapi.json +278 -0
  15. {granttrace-2.4.0 → granttrace-2.4.1}/core/evidence.py +12 -12
  16. {granttrace-2.4.0 → granttrace-2.4.1}/core/generator.py +3 -3
  17. {granttrace-2.4.0 → granttrace-2.4.1}/core/parameters.py +2 -2
  18. {granttrace-2.4.0 → granttrace-2.4.1}/core/parser.py +8 -8
  19. granttrace-2.4.1/docs/release-checklist.md +105 -0
  20. {granttrace-2.4.0 → granttrace-2.4.1}/examples/README.md +5 -3
  21. {granttrace-2.4.0 → granttrace-2.4.1}/examples/sample_report.html +90 -90
  22. {granttrace-2.4.0 → granttrace-2.4.1}/examples/sample_result.json +94 -94
  23. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/PKG-INFO +38 -17
  24. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/SOURCES.txt +7 -0
  25. {granttrace-2.4.0 → granttrace-2.4.1}/pyproject.toml +6 -2
  26. {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_install.py +66 -0
  27. granttrace-2.4.1/tests/test_demo.py +501 -0
  28. granttrace-2.4.1/tests/test_first_run_guidance.py +162 -0
  29. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_rc2_security_hardening.py +1 -1
  30. granttrace-2.4.0/docs/release-checklist.md +0 -74
  31. {granttrace-2.4.0 → granttrace-2.4.1}/LICENSE +0 -0
  32. {granttrace-2.4.0 → granttrace-2.4.1}/MANIFEST.in +0 -0
  33. {granttrace-2.4.0 → granttrace-2.4.1}/SECURITY.md +0 -0
  34. {granttrace-2.4.0 → granttrace-2.4.1}/config.example.json +0 -0
  35. {granttrace-2.4.0 → granttrace-2.4.1}/config.schema.json +0 -0
  36. {granttrace-2.4.0 → granttrace-2.4.1}/core/config_scaffold.py +0 -0
  37. {granttrace-2.4.0 → granttrace-2.4.1}/core/diff.py +0 -0
  38. {granttrace-2.4.0 → granttrace-2.4.1}/core/models.py +0 -0
  39. {granttrace-2.4.0 → granttrace-2.4.1}/core/reporter.py +0 -0
  40. {granttrace-2.4.0 → granttrace-2.4.1}/core/transactions.py +0 -0
  41. {granttrace-2.4.0 → granttrace-2.4.1}/docs/advanced.md +0 -0
  42. {granttrace-2.4.0 → granttrace-2.4.1}/docs/assets/report-preview.png +0 -0
  43. {granttrace-2.4.0 → granttrace-2.4.1}/docs/business-scenarios.md +0 -0
  44. {granttrace-2.4.0 → granttrace-2.4.1}/docs/configuration.md +0 -0
  45. {granttrace-2.4.0 → granttrace-2.4.1}/docs/parameter-serialization.md +0 -0
  46. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/dependency_links.txt +0 -0
  47. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/entry_points.txt +0 -0
  48. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/requires.txt +0 -0
  49. {granttrace-2.4.0 → granttrace-2.4.1}/granttrace.egg-info/top_level.txt +0 -0
  50. {granttrace-2.4.0 → granttrace-2.4.1}/mock_server/__init__.py +0 -0
  51. {granttrace-2.4.0 → granttrace-2.4.1}/mock_server/server.py +0 -0
  52. {granttrace-2.4.0 → granttrace-2.4.1}/openapi.json +0 -0
  53. {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_business_scenarios.py +0 -0
  54. {granttrace-2.4.0 → granttrace-2.4.1}/scripts/verify_examples.py +0 -0
  55. {granttrace-2.4.0 → granttrace-2.4.1}/setup.cfg +0 -0
  56. {granttrace-2.4.0 → granttrace-2.4.1}/test_suite.py +0 -0
  57. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_auditor_integration.py +0 -0
  58. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_business_authorization.py +0 -0
  59. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_config_scaffold.py +0 -0
  60. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_config_validation.py +0 -0
  61. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_diff.py +0 -0
  62. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_diff_hardening.py +0 -0
  63. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_evidence.py +0 -0
  64. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_parameter_serialization.py +0 -0
  65. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_parser_generator_hardening.py +0 -0
  66. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_rc3_hardening.py +0 -0
  67. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_bola.py +0 -0
  68. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_config.py +0 -0
  69. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_evidence.py +0 -0
  70. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_identity.py +0 -0
  71. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_release_transactions.py +0 -0
  72. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_report_usability.py +0 -0
  73. {granttrace-2.4.0 → granttrace-2.4.1}/tests/test_schema_regressions.py +0 -0
  74. {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
@@ -28,7 +28,7 @@ python -m coverage xml
28
28
  python -m coverage json
29
29
  ```
30
30
 
31
- Mypy is advisory while existing dynamic JSON type issues are tracked:
31
+ Mypy is a blocking CI check and must report zero errors; coverage must be at least 80%:
32
32
 
33
33
  ```bash
34
34
  python -m mypy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: granttrace
3
- Version: 2.4.0
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
  ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
- git clone https://github.com/ysc070528/granttrace.git
56
- cd granttrace
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
- granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --export-json result.local.json
63
+ python -m pip install dist/granttrace-2.4.1-py3-none-any.whl
64
+ granttrace demo
65
65
  ```
66
66
 
67
- 用浏览器打开默认生成的 **`granttrace_report.html`**:先查看发现与修复建议,再按状态筛选端点、展开证据。预期结果为 **1 个 CONFIRMED、1 个 PUBLIC、1 个 SECURE、2 个 SKIPPED**,确定性覆盖率 **60%**。两个 PATCH 因默认只读而跳过。JSON 结果写入 `result.local.json`;两个本地产物均由 `.gitignore` 忽略。
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
- 再次打开 **`granttrace_report.html`**。预期 **2 个 CONFIRMED、1 个 PUBLIC、2 个 SECURE**,确定性覆盖率 **100%**,批量赋值发现的 `rollback_verified` 为 `true`。本次扫描更新同名 HTML / JSON 文件;这些结果只属于内置五个演示端点。
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
- 账户、API Key / Cookie、合法共享和管理员访问配置见 [配置指南](docs/configuration.md)。支持的数组、布尔值及 `style` / `explode` 编码见 [参数规则](docs/parameter-serialization.md)。接入实测前请核对 [安全边界](SECURITY.md)。
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
  ![License](https://img.shields.io/badge/license-Apache--2.0-green)
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
- git clone https://github.com/ysc070528/granttrace.git
29
- cd granttrace
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
- granttrace --spec openapi.json --target http://127.0.0.1:8080 --config config.example.json --export-json result.local.json
36
+ python -m pip install dist/granttrace-2.4.1-py3-none-any.whl
37
+ granttrace demo
38
38
  ```
39
39
 
40
- 用浏览器打开默认生成的 **`granttrace_report.html`**:先查看发现与修复建议,再按状态筛选端点、展开证据。预期结果为 **1 个 CONFIRMED、1 个 PUBLIC、1 个 SECURE、2 个 SKIPPED**,确定性覆盖率 **60%**。两个 PATCH 因默认只读而跳过。JSON 结果写入 `result.local.json`;两个本地产物均由 `.gitignore` 忽略。
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
- 再次打开 **`granttrace_report.html`**。预期 **2 个 CONFIRMED、1 个 PUBLIC、2 个 SECURE**,确定性覆盖率 **100%**,批量赋值发现的 `rollback_verified` 为 `true`。本次扫描更新同名 HTML / JSON 文件;这些结果只属于内置五个演示端点。
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
- 账户、API Key / Cookie、合法共享和管理员访问配置见 [配置指南](docs/configuration.md)。支持的数组、布尔值及 `style` / `explode` 编码见 [参数规则](docs/parameter-serialization.md)。接入实测前请核对 [安全边界](SECURITY.md)。
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
- args = build_argument_parser().parse_args(argv)
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,
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """GrantTrace core package."""
3
3
 
4
- __version__ = "2.4.0"
4
+ __version__ = "2.4.1"
@@ -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
- payload = item.get("payload") if isinstance(item.get("payload"), dict) else item
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 valid_name and location in ("path", "query"):
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(