granttrace 2.6.0__tar.gz → 2.7.0__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.6.0 → granttrace-2.7.0}/CHANGELOG.md +32 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/PKG-INFO +18 -4
- {granttrace-2.6.0 → granttrace-2.7.0}/README.md +17 -3
- {granttrace-2.6.0 → granttrace-2.7.0}/README.zh-CN.md +11 -2
- {granttrace-2.6.0 → granttrace-2.7.0}/SECURITY.md +8 -2
- {granttrace-2.6.0 → granttrace-2.7.0}/VERIFICATION.md +45 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/api_sentinel.py +15 -10
- {granttrace-2.6.0 → granttrace-2.7.0}/config.schema.json +66 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/__init__.py +1 -1
- {granttrace-2.6.0 → granttrace-2.7.0}/core/auditor.py +485 -36
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/base.py +13 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/bola.py +138 -1
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/identities.py +172 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/evidence.py +2 -1
- {granttrace-2.6.0 → granttrace-2.7.0}/core/reporter.py +54 -2
- {granttrace-2.6.0 → granttrace-2.7.0}/core/request_builder.py +53 -6
- {granttrace-2.6.0 → granttrace-2.7.0}/core/results.py +3 -2
- {granttrace-2.6.0 → granttrace-2.7.0}/core/sarif.py +7 -1
- granttrace-2.7.0/core/scan_safety.py +214 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/transport.py +2 -2
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/comparison.md +4 -3
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/configuration.md +100 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/windows-portable.md +15 -10
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/README.md +4 -3
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/sample_report.html +24 -24
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/sample_result.json +19 -19
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/PKG-INFO +18 -4
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/SOURCES.txt +4 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/pyproject.toml +1 -1
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/build_windows_portable.py +12 -5
- granttrace-2.7.0/scripts/source_version.py +86 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_windows_portable.py +12 -3
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_cli_security_hardening.py +1 -1
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_output_snapshot.py +1 -0
- granttrace-2.7.0/tests/test_release_version.py +135 -0
- granttrace-2.7.0/tests/test_v27_features.py +1161 -0
- granttrace-2.7.0/tests/test_v27_hardening.py +680 -0
- granttrace-2.6.0/core/scan_safety.py +0 -127
- {granttrace-2.6.0 → granttrace-2.7.0}/CODE_OF_CONDUCT.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/CONTRIBUTING.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/LICENSE +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/MANIFEST.in +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/config.example.json +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/authorization.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/bopla.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_scaffold.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/__init__.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/readbacks.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validation/spec_checks.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/config_validator.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/demo.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/demo_assets/__init__.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/demo_assets/demo-config.json +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/demo_assets/mock-data.json +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/demo_assets/openapi.json +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/diff.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/generator.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/models.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/onboarding.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/parameters.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/parser.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/remote_refs.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/reproduction.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/transactions.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/core/yaml_safety.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/advanced.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/assets/report-finding.png +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/assets/report-preview.png +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/authorization-audit-hardening.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/authorization-coverage-audit.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/bopla-readonly.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/business-scenarios.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/case-studies/crapi.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/case-studies/vampi.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/history/release-checklist-history.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/history/release-verification-history.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/parameter-serialization.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/pr42-independent-review.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/pr42-state-authority-review.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/pr43-integration-plan.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/pr43-integration-review.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/release-checklist.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/docs/remote-references.md +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/crapi/no-chatbot.compose.yml +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/crapi/prepare_config.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/vampi/prepare_config.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/examples/vampi/run_vampi.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/dependency_links.txt +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/entry_points.txt +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/requires.txt +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/granttrace.egg-info/top_level.txt +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/mock_server/__init__.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/mock_server/server.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/openapi.json +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/benchmark_read_safety.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_authorization_coverage.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_business_scenarios.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_examples.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_install.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_pr42_adversarial.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/scripts/verify_pr43_integration.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/setup.cfg +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/test_suite.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_auditor_integration.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_auth_cookie_references.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_auth_header_references.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_authorization_evidence_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_authorization_maintenance.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_authorization_policy_preflight.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bola_rejection_evidence.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_collection_grouping.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_integration.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_pending_exports.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_policy.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_policy_counterexamples.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_reports.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_bopla_transport_safety.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_business_authorization.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_cli_config_preflight.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_cli_report_boundaries.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_code_review_regressions.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_committed_examples.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_config_readback_preflight.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_config_scaffold.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_config_validation.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_credential_echo_redaction.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_curl_report.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_demo.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_diff.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_diff_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_english_reasons.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_evidence.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_expanded_detection.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_expanded_report_boundaries.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_first_run_guidance.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_header_cookie_array_object.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_header_cookie_boundaries.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_header_cookie_parameters.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_header_cookie_transport.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_late_private_value_redaction.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_mass_assignment_flow.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_merge_patch_recovery.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_module_boundaries.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_not_found_denial.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_onboarding.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_onboarding_cli.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_parameter_compatibility.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_parameter_serialization.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_parser_generator_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_parser_remote_refs.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_parser_replacement_auth.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_patch_readback_strictness.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_pr42_adversarial.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_pr42_state_authority.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_pr43_authorization_evidence.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_pr43_evidence_paths.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_pr43_put_safety.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_problem_details_denials.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_query_auth_parameters.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_rate_limit_retry.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_read_only_path_guards.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_read_only_safety.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_read_safety_cache.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_remote_ref_host_validation.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_remote_refs.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_remote_refs_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_remote_refs_premerge_audit.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_remote_refs_review_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_report_usability.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_reproduction.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_sarif.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_sarif_cli.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_scan_accounting.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_schema_regressions.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_small_review_items.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_spec_config_cross_checks.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_transaction_transport_hardening.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_transport.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_unconfigured_query_parameters.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_version_identity.py +0 -0
- {granttrace-2.6.0 → granttrace-2.7.0}/tests/test_yaml_alias_expansion.py +0 -0
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0 - 2026-11-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- BOLA on query-style POST operations. A POST is sent only when its `bola`
|
|
8
|
+
policy declares `read_only: true` and gives the request body; its path,
|
|
9
|
+
operationId or summary must name a read action (search, query, lookup...)
|
|
10
|
+
and none of its metadata or concrete path may name a mutation. `body` is
|
|
11
|
+
sent unchanged by every identity; `identity_bodies` gives the Owner body
|
|
12
|
+
(repeated by the Visitor's cross request and Anonymous) and an optional
|
|
13
|
+
`visitor_self` body. Anything else is skipped or inconclusive with zero
|
|
14
|
+
requests, and a rate-limited POST is never retried.
|
|
15
|
+
- Sensitive-field leads (`detect_sensitive_exposure`, OWASP API3): unmasked
|
|
16
|
+
sensitive-named Owner values that reach the Visitor at the same JSON path.
|
|
17
|
+
`CONFIRMED` only with an explicit `deny` policy, a confirmed object-level
|
|
18
|
+
decision on the same observations and a value that differs from the
|
|
19
|
+
Visitor's own record; otherwise `SUSPICIOUS`. Values are never reported,
|
|
20
|
+
and the analysis is bounded on untrusted responses.
|
|
21
|
+
- Role matrix (`identities.roles`): for operations with a `required_role`,
|
|
22
|
+
one request per configured role, decided with the BFLA evidence standard
|
|
23
|
+
(valid privileged baseline, clean Anonymous denial, configured protected
|
|
24
|
+
data in the role's valid response). Shown in the HTML report and the JSON
|
|
25
|
+
`rbac_matrix`; confirmed violations are exported to SARIF as `GT-BFLA-001`,
|
|
26
|
+
confirmed sensitive-field findings as `GT-BOPLA-001`.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- The Windows portable build, its verifier, its workflow and the packaged
|
|
31
|
+
README and launcher read the version from `pyproject.toml` and
|
|
32
|
+
`core/__init__.py` (checked to agree) instead of repeating it. A test fails
|
|
33
|
+
on any drift.
|
|
34
|
+
|
|
3
35
|
## 2.6.0 - 2026-11-09
|
|
4
36
|
|
|
5
37
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: granttrace
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Evidence-driven, read-only authorization testing for OpenAPI APIs: BOLA/IDOR, broken authentication, BFLA and property-level checks
|
|
5
5
|
Author: GrantTrace contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -64,6 +64,18 @@ is in the repository.
|
|
|
64
64
|
- **Safe by default.** Scans are read-only. GET operations whose paths,
|
|
65
65
|
identifiers or descriptions suggest side effects are skipped before any
|
|
66
66
|
request, and writes against external targets are refused.
|
|
67
|
+
- **Read-only POST, only when you vouch for it.** A query-style POST (search,
|
|
68
|
+
lookup, query...) is compared across identities only after you declare
|
|
69
|
+
`read_only: true` and give the request body. Its path, operationId or summary
|
|
70
|
+
must name a read action and must not name a mutation; otherwise nothing is
|
|
71
|
+
sent. These checks reduce risk but cannot prove a POST has no side effects.
|
|
72
|
+
- **Sensitive-field leads.** With `detect_sensitive_exposure`, unmasked
|
|
73
|
+
sensitive-looking Owner fields (phone, salary, credentials...) that reach the
|
|
74
|
+
Visitor are reported as leads. They are confirmed only on top of a confirmed
|
|
75
|
+
object-level finding and never from a field name alone.
|
|
76
|
+
- **Role matrix.** Optional `identities.roles` are probed on operations with a
|
|
77
|
+
`required_role`. A lower role is a violation only when it receives the
|
|
78
|
+
privileged baseline's configured protected data, not merely an HTTP 200.
|
|
67
79
|
- **Plan before you send.** `--init-config` drafts a configuration and a review
|
|
68
80
|
checklist from the specification, `--validate-config` checks it offline, and
|
|
69
81
|
`--dry-run` shows what would be requested while sending nothing.
|
|
@@ -82,8 +94,10 @@ is in the repository.
|
|
|
82
94
|
| BOLA / IDOR | CWE-639 | Read-only, as Owner, Visitor and Anonymous | Two test accounts and a resource each of them owns |
|
|
83
95
|
| Broken Authentication | CWE-306 | Read-only, on operations the specification protects | `private_data_paths`: Owner values Anonymous must not see |
|
|
84
96
|
| Broken Function Level Authorization | CWE-285 | Read-only | The required role and each identity's role |
|
|
97
|
+
| Role matrix (RBAC) | CWE-285 | Read-only, one request per configured role | `identities.roles`, `required_role` and `private_data_paths` |
|
|
98
|
+
| BOLA on query-style POST | CWE-639 | Declared `read_only: true` POST with a read action in its name | The request `body` (or `identity_bodies`) |
|
|
85
99
|
| Private data in collections | CWE-639 | Read-only, on list endpoints | `private_data_paths` |
|
|
86
|
-
| Property-level authorization (BOPLA) | CWE-200 | Read-only | Per-field rules in `bopla` |
|
|
100
|
+
| Property-level authorization (BOPLA) | CWE-200 | Read-only | Per-field rules in `bopla`; `detect_sensitive_exposure` adds heuristic leads |
|
|
87
101
|
| Mass Assignment | CWE-915 | **Built-in demo only** | – |
|
|
88
102
|
|
|
89
103
|
Mass assignment testing writes to a resource, so GrantTrace only does it when it
|
|
@@ -93,8 +107,8 @@ before any write; the built-in demo, which owns its data, shows the complete
|
|
|
93
107
|
flow with independent readback and verified rollback.
|
|
94
108
|
|
|
95
109
|
**Not supported yet:** GraphQL and gRPC; OAuth, OIDC or mTLS login flows
|
|
96
|
-
(configure tokens or headers yourself); API keys in the query string; POST
|
|
97
|
-
DELETE tests; writes against external APIs.
|
|
110
|
+
(configure tokens or headers yourself); API keys in the query string; POST
|
|
111
|
+
other than declared read-only queries; DELETE tests; writes against external APIs.
|
|
98
112
|
|
|
99
113
|
## Install
|
|
100
114
|
|
|
@@ -37,6 +37,18 @@ is in the repository.
|
|
|
37
37
|
- **Safe by default.** Scans are read-only. GET operations whose paths,
|
|
38
38
|
identifiers or descriptions suggest side effects are skipped before any
|
|
39
39
|
request, and writes against external targets are refused.
|
|
40
|
+
- **Read-only POST, only when you vouch for it.** A query-style POST (search,
|
|
41
|
+
lookup, query...) is compared across identities only after you declare
|
|
42
|
+
`read_only: true` and give the request body. Its path, operationId or summary
|
|
43
|
+
must name a read action and must not name a mutation; otherwise nothing is
|
|
44
|
+
sent. These checks reduce risk but cannot prove a POST has no side effects.
|
|
45
|
+
- **Sensitive-field leads.** With `detect_sensitive_exposure`, unmasked
|
|
46
|
+
sensitive-looking Owner fields (phone, salary, credentials...) that reach the
|
|
47
|
+
Visitor are reported as leads. They are confirmed only on top of a confirmed
|
|
48
|
+
object-level finding and never from a field name alone.
|
|
49
|
+
- **Role matrix.** Optional `identities.roles` are probed on operations with a
|
|
50
|
+
`required_role`. A lower role is a violation only when it receives the
|
|
51
|
+
privileged baseline's configured protected data, not merely an HTTP 200.
|
|
40
52
|
- **Plan before you send.** `--init-config` drafts a configuration and a review
|
|
41
53
|
checklist from the specification, `--validate-config` checks it offline, and
|
|
42
54
|
`--dry-run` shows what would be requested while sending nothing.
|
|
@@ -55,8 +67,10 @@ is in the repository.
|
|
|
55
67
|
| BOLA / IDOR | CWE-639 | Read-only, as Owner, Visitor and Anonymous | Two test accounts and a resource each of them owns |
|
|
56
68
|
| Broken Authentication | CWE-306 | Read-only, on operations the specification protects | `private_data_paths`: Owner values Anonymous must not see |
|
|
57
69
|
| Broken Function Level Authorization | CWE-285 | Read-only | The required role and each identity's role |
|
|
70
|
+
| Role matrix (RBAC) | CWE-285 | Read-only, one request per configured role | `identities.roles`, `required_role` and `private_data_paths` |
|
|
71
|
+
| BOLA on query-style POST | CWE-639 | Declared `read_only: true` POST with a read action in its name | The request `body` (or `identity_bodies`) |
|
|
58
72
|
| Private data in collections | CWE-639 | Read-only, on list endpoints | `private_data_paths` |
|
|
59
|
-
| Property-level authorization (BOPLA) | CWE-200 | Read-only | Per-field rules in `bopla` |
|
|
73
|
+
| Property-level authorization (BOPLA) | CWE-200 | Read-only | Per-field rules in `bopla`; `detect_sensitive_exposure` adds heuristic leads |
|
|
60
74
|
| Mass Assignment | CWE-915 | **Built-in demo only** | – |
|
|
61
75
|
|
|
62
76
|
Mass assignment testing writes to a resource, so GrantTrace only does it when it
|
|
@@ -66,8 +80,8 @@ before any write; the built-in demo, which owns its data, shows the complete
|
|
|
66
80
|
flow with independent readback and verified rollback.
|
|
67
81
|
|
|
68
82
|
**Not supported yet:** GraphQL and gRPC; OAuth, OIDC or mTLS login flows
|
|
69
|
-
(configure tokens or headers yourself); API keys in the query string; POST
|
|
70
|
-
DELETE tests; writes against external APIs.
|
|
83
|
+
(configure tokens or headers yourself); API keys in the query string; POST
|
|
84
|
+
other than declared read-only queries; DELETE tests; writes against external APIs.
|
|
71
85
|
|
|
72
86
|
## Install
|
|
73
87
|
|
|
@@ -30,6 +30,13 @@ Demo 在 `127.0.0.1` 启动一个临时 API,找出其中的 BOLA 和 Mass Assi
|
|
|
30
30
|
证据不足时判为 `SUSPICIOUS` 或 `INCONCLUSIVE`,不会算作漏洞。
|
|
31
31
|
- **默认安全。** 扫描只读。路径、operationId 或描述带有副作用语义的 GET 在请求前跳过,
|
|
32
32
|
对外部目标的写入一律拒绝。
|
|
33
|
+
- **只读 POST 需由你声明。** 查询类 POST(search、lookup、query 等)只有在你声明 `read_only: true`
|
|
34
|
+
并提供请求体后才会跨身份比较;其路径、operationId 或 summary 必须表明读取动作且不得表明修改,
|
|
35
|
+
否则不发送任何请求。这些检查只能降低风险,不能证明 POST 没有副作用。
|
|
36
|
+
- **敏感字段线索。** 开启 `detect_sensitive_exposure` 后,Visitor 收到的未脱敏、名称敏感的 Owner 字段
|
|
37
|
+
(电话、薪资、凭据等)作为线索报告;只有在对象级越权已确认时才会确认,绝不仅凭字段名确认。
|
|
38
|
+
- **角色矩阵。** 可选的 `identities.roles` 只在配置了 `required_role` 的操作上探测。低权限角色只有拿到
|
|
39
|
+
特权基线中配置的受保护数据才算违规,单纯的 HTTP 200 不算。
|
|
33
40
|
- **先计划,再发送。** `--init-config` 根据规范生成配置草稿和核对清单,`--validate-config`
|
|
34
41
|
离线校验,`--dry-run` 列出将要发送的请求,但不发送任何请求。
|
|
35
42
|
- **报告可直接用于修复。** 每条结果说明比较了什么;每个确认漏洞附修复方向、复验步骤、
|
|
@@ -42,10 +49,12 @@ Demo 在 `127.0.0.1` 启动一个临时 API,找出其中的 BOLA 和 Mass Assi
|
|
|
42
49
|
| 检查 | CWE | 运行方式 | 需要配置 |
|
|
43
50
|
|---|---|---|---|
|
|
44
51
|
| BOLA / IDOR | CWE-639 | 只读,以 Owner、Visitor、Anonymous 请求 | 两个测试账号及各自拥有的资源 |
|
|
52
|
+
| 查询类 POST 的 BOLA | CWE-639 | 声明 `read_only: true` 且名称表明读取动作的 POST | 请求体 `body`(或 `identity_bodies`) |
|
|
45
53
|
| Broken Authentication | CWE-306 | 只读,针对规范要求认证的操作 | `private_data_paths`:匿名不应看到的 Owner 数据 |
|
|
46
54
|
| Broken Function Level Authorization | CWE-285 | 只读 | 所需角色及各身份的角色 |
|
|
55
|
+
| 角色矩阵(RBAC) | CWE-285 | 只读,每个配置角色一次请求 | `identities.roles`、`required_role` 与 `private_data_paths` |
|
|
47
56
|
| 集合接口私有数据泄露 | CWE-639 | 只读,针对列表接口 | `private_data_paths` |
|
|
48
|
-
| 字段级授权(BOPLA) | CWE-200 | 只读 | `bopla`
|
|
57
|
+
| 字段级授权(BOPLA) | CWE-200 | 只读 | `bopla` 中按字段的规则;`detect_sensitive_exposure` 提供启发式线索 |
|
|
49
58
|
| Mass Assignment | CWE-915 | **仅内置 demo** | – |
|
|
50
59
|
|
|
51
60
|
Mass Assignment 测试需要写入资源,GrantTrace 只在能核验资源实际状态并恢复时才执行。
|
|
@@ -53,7 +62,7 @@ Mass Assignment 测试需要写入资源,GrantTrace 只在能核验资源实
|
|
|
53
62
|
自己持有数据的内置 demo 展示完整流程,包括独立读回和已核验的恢复。
|
|
54
63
|
|
|
55
64
|
**暂不支持:** GraphQL 和 gRPC;OAuth、OIDC、mTLS 登录流程(请自行配置 token 或 header);
|
|
56
|
-
查询字符串中的 API key;
|
|
65
|
+
查询字符串中的 API key;声明为只读的查询以外的 POST;DELETE 测试;对外部 API 的写入。
|
|
57
66
|
|
|
58
67
|
## 安装
|
|
59
68
|
|
|
@@ -9,8 +9,14 @@ cannot independently verify an external API's authoritative resource state, so
|
|
|
9
9
|
even with `--allow-write-tests`, an explicit write allowlist, readback mappings
|
|
10
10
|
and `consistency: "strong"`, allowlisted PATCH and PUT checks are reported as
|
|
11
11
|
`INCONCLUSIVE` (`unverified_state_authority`) and send nothing. The built-in demo,
|
|
12
|
-
which owns its data, is the only place the full flow runs.
|
|
13
|
-
|
|
12
|
+
which owns its data, is the only place the full flow runs. DELETE is never sent.
|
|
13
|
+
A POST is sent only for the read-only BOLA comparison, and only when the
|
|
14
|
+
operation's policy declares `read_only: true` with an explicit request body, its
|
|
15
|
+
path, operationId or summary names a read action, and neither its metadata nor
|
|
16
|
+
its concrete path names a mutation. These checks refuse obviously
|
|
17
|
+
state-changing operations but cannot prove that a POST has no side effects:
|
|
18
|
+
declare `read_only` only for operations you have confirmed change nothing. A
|
|
19
|
+
POST is never retried.
|
|
14
20
|
|
|
15
21
|
Where the flow does run, every operation must be in the explicit write
|
|
16
22
|
allowlist, and active checks require a JSON request schema plus a configured GET
|
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
Detailed historical release verification records are archived in
|
|
4
4
|
[release verification history](docs/history/release-verification-history.md).
|
|
5
5
|
|
|
6
|
+
## v2.7.0 pre-release acceptance - 2026-11-11
|
|
7
|
+
|
|
8
|
+
Status: **accepted for release; v2.7.0 is not published.** Release candidate:
|
|
9
|
+
commit `750a801` (head of PR #90: main `9fc560c`, the merge of #84, plus the
|
|
10
|
+
release date, the publishing workflow's manual choice and release-specific
|
|
11
|
+
documentation); the commit after it adds only this record. PyPI's latest is
|
|
12
|
+
2.6.0; there is no `v2.7.0` tag or Release.
|
|
13
|
+
|
|
14
|
+
Environment: Linux x86_64 container; CPython **3.13.18** for coverage, linting,
|
|
15
|
+
acceptance scripts and the build, plus CPython **3.9.25** and **3.14.6**; PyYAML
|
|
16
|
+
**6.0.3**, pytest **9.2.0** (**8.4.2** on 3.9), coverage **7.17.2**, Ruff
|
|
17
|
+
**0.17.4**, Mypy **2.4.1**, build **1.6.1**, Twine **7.0.0**, setuptools
|
|
18
|
+
**84.2.1**, wheel **0.48.0**. The Windows package was built and checked by the
|
|
19
|
+
GitHub-hosted `windows-2025` runner; no Windows host was used locally.
|
|
20
|
+
|
|
21
|
+
| Check | Result |
|
|
22
|
+
|---|---|
|
|
23
|
+
| CI on main after #84 (`9fc560c`) | GrantTrace CI [run 42918318210](https://github.com/ysc070528/granttrace/actions/runs/42918318210) (Ruff, Mypy, tests on 3.9 / 3.12 / 3.14, runtime dependency audit), Windows x64 portable [run 42918318200](https://github.com/ysc070528/granttrace/actions/runs/42918318200) and CodeQL [run 42918318197](https://github.com/ysc070528/granttrace/actions/runs/42918318197): all passed |
|
|
24
|
+
| Full regression suite | 1322 passed and 2402 subtests passed on CPython 3.13.18 and 3.14.6; 1322 passed on 3.9.25; full unittest passed |
|
|
25
|
+
| Branch-aware coverage | 89.95% combined (statements 7281 / 7922, branches 3474 / 4034 = 86.12%); required minimum 80% |
|
|
26
|
+
| Acceptance scripts | PR42 adversarial; PR43 integration (89 tests, source `750a801`, clean tree); authorization coverage, authoritative and generic HTTP; business scenarios 6 / 6; JSON / YAML examples; installation |
|
|
27
|
+
| Wheel / sdist | Isolated `python -m build` from `git archive`; `twine check --strict` passed for both. Wheel: 43 files; sdist: 195 files; no caches, local configurations or build output. Metadata: `Version: 2.7.0`, the unchanged `Summary`, `Requires-Python: >=3.9`, `Requires-Dist: PyYAML>=6.0` |
|
|
28
|
+
| Clean installations | The wheel in new virtual environments on CPython 3.9.25, 3.13.18 and 3.14.6, and the sdist on 3.13.18, each outside the checkout: `granttrace --version` prints `GrantTrace 2.7.0` and `core` loads from `site-packages`; `granttrace demo` exits 0 with BOLA and mass assignment confirmed, rollback verified, the database restored and the server stopped. `scripts/verify_install.py --wheel` passed on the built wheel: versions consistent, 0 PATCH in read-only and external active scans (the external active scan is blocked without state authority), 0 requests for the offline plan |
|
|
29
|
+
| Version consistency | `pyproject.toml`, `core.__version__`, CLI, HTML and JSON reports and the HTTP User-Agent say 2.7.0. The Windows build and verification scripts, the Windows workflow's ZIP and artifact names and the packaged README and launcher derive it through `scripts/source_version.py`; the publishing workflow's manual choices include v2.7.0 |
|
|
30
|
+
| Windows x64 portable | Built from `9fc560c` (clean tree) in run 42918318200 on Windows Server 2025 with Python 3.14.5 and PyInstaller 6.22.3: `GrantTrace-v2.7.0-Windows-x64.zip`, 10,747,513 bytes, SHA256 `a05d6288b85577e6e4bee1a12c9910c8922e564cfa0cf475f96e180832bc1a90`. `scripts/verify_windows_portable.py` passed: extraction to paths with spaces and Chinese characters outside the checkout, no Python on the child PATH, `--version` and `--help`, active and read-only demos, an offline YAML plan with 0 requests, `Start-Demo.bat`, the loopback port closed and no package process left. The artifact host was not reachable from this environment, so the ZIP was not downloaded and rehashed here, and no one double-clicked it on a clean Windows machine |
|
|
31
|
+
| 2.7.0 detectors | Read-only POST, role matrix and sensitive-field checks are covered by `tests/test_v27_hardening.py`, including real HTTP to a loopback server. VAmPI and crAPI were not re-run for this candidate; the read-only GET/HEAD decisions are unchanged (a 200,000-endpoint differential against 2.6.0 found no difference) |
|
|
32
|
+
| Secrets | A pattern scan of all 194 tracked files found no keys, tokens or JWTs; the only match is a dummy private-key marker in a redaction test. GitHub secret scanning is not enabled for the repository |
|
|
33
|
+
| Documentation | Relative links, anchors and links to this repository: 0 problems. `SECURITY.md` describes when a read-only POST is sent; release-specific pages updated in #90 |
|
|
34
|
+
|
|
35
|
+
Local candidate hashes, built from `750a801` in the container:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
8efe69a82a953310156424bfb2451f6bee842d2e4655445f25daacddba9fe303 granttrace-2.7.0-py3-none-any.whl
|
|
39
|
+
2c72b227652da135275c17036f58232e051efa53f325aa24ca7ea60c7f183b92 granttrace-2.7.0.tar.gz
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The publishing workflow builds the uploaded distributions from the tag. The
|
|
43
|
+
Release's Windows ZIP and `SHA256SUMS.txt` come from the Windows workflow run on
|
|
44
|
+
the release commit itself; their hashes differ from the build above because the
|
|
45
|
+
build records its source commit.
|
|
46
|
+
|
|
47
|
+
Still to do: CI on the release commit, then the tag, the GitHub Release with the
|
|
48
|
+
Windows assets, PyPI publishing and the post-release checks in the
|
|
49
|
+
[release checklist](docs/release-checklist.md), including a clean Windows machine.
|
|
50
|
+
|
|
6
51
|
## v2.6.0 pre-release acceptance - 2026-11-08
|
|
7
52
|
|
|
8
53
|
Status: **accepted for release; v2.6.0 is not published.** Release candidate:
|
|
@@ -671,6 +671,7 @@ def _export_reports(args: argparse.Namespace, auditor: APISentinelAuditor, spec_
|
|
|
671
671
|
)
|
|
672
672
|
report_target = ("[BOPLA TARGET OMITTED]" if auditor.bopla_config else
|
|
673
673
|
sanitize_url(args.target, secret_values=auditor.secret_values()))
|
|
674
|
+
report_rbac = auditor.rbac_matrix_record()
|
|
674
675
|
SecurityReportGenerator.generate(
|
|
675
676
|
stats=report_stats,
|
|
676
677
|
findings=report_findings,
|
|
@@ -679,23 +680,27 @@ def _export_reports(args: argparse.Namespace, auditor: APISentinelAuditor, spec_
|
|
|
679
680
|
output_path=args.output,
|
|
680
681
|
reproduction_templates=reproduction_templates,
|
|
681
682
|
secret_values=auditor.secret_values(),
|
|
683
|
+
rbac_matrix=report_rbac,
|
|
682
684
|
)
|
|
683
685
|
print(f"[OK] HTML report: {args.output}")
|
|
684
686
|
|
|
685
687
|
if args.export_json:
|
|
686
688
|
_ensure_parent(args.export_json)
|
|
687
689
|
with open(args.export_json, "w", encoding="utf-8") as handle:
|
|
690
|
+
json_payload = {
|
|
691
|
+
"tool_version": __version__,
|
|
692
|
+
"report_schema_version": 2,
|
|
693
|
+
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
694
|
+
"elapsed_seconds": round(elapsed, 3),
|
|
695
|
+
"target": report_target,
|
|
696
|
+
"stats": report_stats,
|
|
697
|
+
"results": report_results,
|
|
698
|
+
"findings": report_findings,
|
|
699
|
+
}
|
|
700
|
+
if report_rbac:
|
|
701
|
+
json_payload["rbac_matrix"] = report_rbac
|
|
688
702
|
json.dump(
|
|
689
|
-
|
|
690
|
-
"tool_version": __version__,
|
|
691
|
-
"report_schema_version": 2,
|
|
692
|
-
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
693
|
-
"elapsed_seconds": round(elapsed, 3),
|
|
694
|
-
"target": report_target,
|
|
695
|
-
"stats": report_stats,
|
|
696
|
-
"results": report_results,
|
|
697
|
-
"findings": report_findings,
|
|
698
|
-
},
|
|
703
|
+
json_payload,
|
|
699
704
|
handle,
|
|
700
705
|
indent=2,
|
|
701
706
|
ensure_ascii=False,
|
|
@@ -28,6 +28,17 @@
|
|
|
28
28
|
},
|
|
29
29
|
"anonymous": {
|
|
30
30
|
"$ref": "#/definitions/anonymous_identity"
|
|
31
|
+
},
|
|
32
|
+
"roles": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "Additional named roles for the RBAC matrix. Names must not reuse owner, visitor, anonymous or roles; probes run only for operations with an explicit required_role.",
|
|
35
|
+
"propertyNames": {
|
|
36
|
+
"pattern": "^[a-zA-Z0-9_.-]+$",
|
|
37
|
+
"not": {"enum": ["owner", "visitor", "anonymous", "roles"]}
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": {
|
|
40
|
+
"$ref": "#/definitions/role_identity"
|
|
41
|
+
}
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
},
|
|
@@ -72,6 +83,39 @@
|
|
|
72
83
|
}
|
|
73
84
|
},
|
|
74
85
|
"definitions": {
|
|
86
|
+
"role_identity": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"id": {
|
|
91
|
+
"type": ["string", "integer", "null"],
|
|
92
|
+
"description": "Resource identifier associated with this role identity"
|
|
93
|
+
},
|
|
94
|
+
"role": {
|
|
95
|
+
"type": ["string", "null"],
|
|
96
|
+
"description": "Role compared with required_role (defaults to the role's key)"
|
|
97
|
+
},
|
|
98
|
+
"token": {
|
|
99
|
+
"type": ["string", "null"],
|
|
100
|
+
"description": "Bearer token or Authorization header value for this role"
|
|
101
|
+
},
|
|
102
|
+
"headers": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"description": "Arbitrary authentication or session headers for this role",
|
|
105
|
+
"additionalProperties": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"parameters": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"description": "Role-bound parameters"
|
|
112
|
+
},
|
|
113
|
+
"body": {
|
|
114
|
+
"description": "Request body this role sends to a read-only POST instead of the Owner body; ignored for GET",
|
|
115
|
+
"type": ["object", "array", "string", "number", "null"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
75
119
|
"authenticated_identity": {
|
|
76
120
|
"type": "object",
|
|
77
121
|
"additionalProperties": false,
|
|
@@ -379,6 +423,28 @@
|
|
|
379
423
|
"type": "string",
|
|
380
424
|
"minLength": 1,
|
|
381
425
|
"description": "Operator-verified role of the configured Visitor identity. Role comparison is exact, without inferring a role hierarchy."
|
|
426
|
+
},
|
|
427
|
+
"read_only": {
|
|
428
|
+
"type": "boolean",
|
|
429
|
+
"description": "POST only: declares the operation side-effect free so the BOLA audit may send it. Its path, operationId or summary must also name a read action (search, query, lookup...) and must not name a mutation."
|
|
430
|
+
},
|
|
431
|
+
"body": {
|
|
432
|
+
"description": "POST only: literal JSON request body sent unchanged by Owner, Visitor and Anonymous. It is never reinterpreted as a per-identity map.",
|
|
433
|
+
"type": ["object", "array", "string", "number"]
|
|
434
|
+
},
|
|
435
|
+
"identity_bodies": {
|
|
436
|
+
"type": "object",
|
|
437
|
+
"description": "POST only, instead of body: 'owner' selects the Owner's object and is also sent by the Visitor's cross request and Anonymous; optional 'visitor_self' selects the Visitor's own object.",
|
|
438
|
+
"additionalProperties": false,
|
|
439
|
+
"required": ["owner"],
|
|
440
|
+
"properties": {
|
|
441
|
+
"owner": {"type": ["object", "array", "string", "number"]},
|
|
442
|
+
"visitor_self": {"type": ["object", "array", "string", "number"]}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
"detect_sensitive_exposure": {
|
|
446
|
+
"type": "boolean",
|
|
447
|
+
"description": "Heuristic lead for unmasked sensitive-named Owner fields returned to the Visitor (OWASP API3). CONFIRMED only with expected_visitor_access 'deny', a confirmed object-level decision and a value that differs from the Visitor's own record; otherwise SUSPICIOUS."
|
|
382
448
|
}
|
|
383
449
|
}
|
|
384
450
|
}
|