coretrace-python-analyzer 0.22.0__tar.gz → 0.23.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.
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/CHANGELOG.md +46 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/docs/plugins.md +12 -3
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/docs/usage.md +89 -16
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/__init__.py +1 -1
- coretrace_python_analyzer-0.23.0/src/coretrace_python/abstract/module_constants.py +121 -0
- coretrace_python_analyzer-0.23.0/src/coretrace_python/abstract/strings.py +68 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +27 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +2 -1
- coretrace_python_analyzer-0.23.0/src/coretrace_python/bundled/security/code_injection/code_injection.py +100 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +1 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +1 -1
- coretrace_python_analyzer-0.23.0/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +115 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cache.py +14 -14
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cli.py +33 -5
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/engine.py +197 -60
- coretrace_python_analyzer-0.23.0/src/coretrace_python/findings/context.py +57 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/refutation.py +210 -27
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/frontend/parser.py +7 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/interprocedural/callgraph.py +14 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/interprocedural/summaries.py +91 -19
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/defuse.py +26 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/lowering.py +2 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/model.py +5 -2
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/printer.py +4 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/__init__.py +7 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/api.py +16 -3
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/detectors.py +21 -3
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/secrets.py +17 -11
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/reporters/sarif.py +15 -3
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/semantic/symbols.py +2 -1
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/engine.py +114 -27
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/models.py +27 -3
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/luigi.json +7 -0
- coretrace_python_analyzer-0.23.0/tests/test_allowlist_shapes.py +402 -0
- coretrace_python_analyzer-0.23.0/tests/test_attribute_call_loops.py +91 -0
- coretrace_python_analyzer-0.23.0/tests/test_attribute_call_review.py +111 -0
- coretrace_python_analyzer-0.23.0/tests/test_attribute_call_sinks.py +271 -0
- coretrace_python_analyzer-0.23.0/tests/test_cache_engine_version.py +42 -0
- coretrace_python_analyzer-0.23.0/tests/test_code_context.py +152 -0
- coretrace_python_analyzer-0.23.0/tests/test_code_context_single_file.py +58 -0
- coretrace_python_analyzer-0.23.0/tests/test_credential_project_path.py +91 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_detectors.py +8 -1
- coretrace_python_analyzer-0.23.0/tests/test_dynamic_import_sinks.py +176 -0
- coretrace_python_analyzer-0.23.0/tests/test_dynamic_import_verdicts.py +101 -0
- coretrace_python_analyzer-0.23.0/tests/test_import_path_sinks.py +108 -0
- coretrace_python_analyzer-0.23.0/tests/test_jobs_option.py +171 -0
- coretrace_python_analyzer-0.23.0/tests/test_jobs_workers.py +99 -0
- coretrace_python_analyzer-0.23.0/tests/test_nonlocal_kept_value.py +116 -0
- coretrace_python_analyzer-0.23.0/tests/test_parse_warnings.py +59 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.23.0/tests/test_selection_sink_proofs.py +69 -0
- coretrace_python_analyzer-0.23.0/tests/test_sink_called_result.py +66 -0
- coretrace_python_analyzer-0.23.0/tests/test_sink_merge.py +72 -0
- coretrace_python_analyzer-0.23.0/tests/test_summary_reachability.py +81 -0
- coretrace_python_analyzer-0.23.0/tests/test_unsafe_xml_risk.py +120 -0
- coretrace_python_analyzer-0.23.0/tests/test_vendored_context.py +170 -0
- coretrace_python_analyzer-0.22.0/src/coretrace_python/abstract/strings.py +0 -98
- coretrace_python_analyzer-0.22.0/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -19
- coretrace_python_analyzer-0.22.0/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -35
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/AUTHORS.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/CODE_OF_CONDUCT.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/CONTRIBUTING.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/README.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/SECURITY.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/analysis.stderr +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/findings.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/rapport.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/sbom.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/triage.csv +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/verification.txt +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/reports/mitmproxy-2026-10-02/vex.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/advisories.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/correlation.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/graph.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/provenance.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/source/positions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/commands.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/src/coretrace_python/taint/urls.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_advisory_url_host.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_argument_present_conditions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_attacker_arguments.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_base_patterns.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_baseline_digests.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_baseline_key.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_baseline_schema_1.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_block_super.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_code_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_command_arguments.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_command_decided_cases.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_command_injection_nature.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_config_value_spans.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_declared_entry_points.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_deep_nesting.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_entry_point_aliases.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_entry_point_suffixes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_file_locations.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_folded_secret_literals.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_function_aliases_api.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_key_taint.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_keyword_name_conditions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_line_numbering.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_lock_dependency_maps.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_lock_positions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_members.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_module_identity.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_open_redirect_destinations.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_password_hashes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_powershell_file_operand.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_provenance.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_relative_root.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_sarif_injection.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_sequence_conditions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_settings_scope.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_ssrf_destinations.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_structured_positions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_tar_implementations.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_template_context_flows.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_template_filter_calls.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_template_filter_overrides.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_template_request_processor.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_unreadable_templates.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_url_proof.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_url_secret_placeholders.py +0 -0
- {coretrace_python_analyzer-0.22.0 → coretrace_python_analyzer-0.23.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.23.0 (2026-10-16)
|
|
4
|
+
|
|
5
|
+
### Compatibility and migration
|
|
6
|
+
|
|
7
|
+
- The cache format changes (16) and the PyIR schema version is 2: caches built by an earlier version miss once. Every cache key also holds the engine version, so this release reanalyses every module of a cached project, which delivers the fixes below to it.
|
|
8
|
+
- `code-injection` reports new sinks (dynamic imports, `runpy`, the import path, `getattr` calls), so findings may appear once; each keeps the rule id `code-injection` and is titled by what the input chooses (#240). Refutation proves more allowlists and fewer mutated collections (#239), `unsafe-xml` messages and confidences change (#242), and a sink in code that cannot run, or a call to a returned callable, is no longer reported (#258, #240). Baselines recorded before should be recorded again after review.
|
|
9
|
+
- Findings carry new metadata, exported in JSON and as SARIF result properties: `context` (`test`, besides a rule's own `example`) for test code (#244), and `origin: vendored` for code under `third_party`, `third-party`, `vendor` or `vendored` (#243). Vendored code is still analysed and reported; the metadata changes no verdict.
|
|
10
|
+
- A project located under a directory named `test`, `tests`, `testing`, `fixtures` or `__tests__` no longer has every credential at low confidence: `hardcoded-credential` and `config-secrets` judge a file's path in the project (#253).
|
|
11
|
+
- Plugin API, all additive:
|
|
12
|
+
- `SymbolCallDetector.describe` returns a `CallReport` (message and confidence) per call (#242);
|
|
13
|
+
- `PluginContext.path(source_id)` gives a file's path from the root of the analysis, and `run_plugins` and `analyze_file` take that root (#253, #244);
|
|
14
|
+
- `Sink` gains `selects` and `requires_called_result`, and `Sink.applying` (#240);
|
|
15
|
+
- summaries gain `ExternalCall.invoked_at` and `NonlocalWrite.kept`, flows `TaintFlow.invoked_at`, and `NonlocalResult` its `previous` value (#240, #258);
|
|
16
|
+
- `coretrace_python.findings.context` classifies paths (`is_test_code`, `code_context`, `code_origin`, `path_in_project`).
|
|
17
|
+
|
|
18
|
+
### Code injection
|
|
19
|
+
|
|
20
|
+
- The module `importlib.import_module`, `__import__` or `runpy.run_module` imports and the file `runpy.run_path` runs are sinks, by position or keyword; the package of a relative name, the globals and the run name are data. Titled `Untrusted module import` and `Untrusted script execution`; a constant package prefix gives a hotspot (#240).
|
|
21
|
+
- The directory `sys.path.insert`, `append` or `extend` adds is a hotspot, `Untrusted import path`, as code runs only when a later import resolves there; `site.addsitedir` is a vulnerability, `Untrusted site directory`, its evidence stating that it runs the `.pth` files of a directory the attacker must control. Writes to `sys.path` that are not calls are not covered (#240).
|
|
22
|
+
- `getattr(obj, name)(...)` with a name the input chooses is a hotspot, `Untrusted attribute call`, reported at each call of the result, with `selected_line` and `invoked_line`, in the function and through project functions. A result that is only read, returned or stored before it is called is not reported (#240).
|
|
23
|
+
- These sinks are proven only by a check that limits the value to constants (membership in a constant allowlist, equality with a constant); a check of its shape (`isidentifier()`, `isalnum()`, `re.fullmatch`) leaves a hotspot. For `getattr`, a check where it selects counts past a join, a check before the call counts in the same loop iteration only, and nothing is reported when the selection or the call cannot run (#240).
|
|
24
|
+
- Calling the result of a call, or an item of it, calls what the call returned: its arguments are no longer read as the call's, so `eval('lambda x: x')(data)` or `importlib.import_module('plugins')(data)` reports nothing (#240).
|
|
25
|
+
|
|
26
|
+
### Refutation
|
|
27
|
+
|
|
28
|
+
- A membership test proves the value when every member is a constant that cannot change before the test: a tuple, list or set display, or a dict display by its keys, directly or through `.keys()`; `frozenset`, `set`, `tuple` or `list` of one of these; a module-level tuple or `frozenset` of constants bound once and never rebound. A list, set or dict of the function counts only while the function only reads it; a module-level one never does (#239).
|
|
29
|
+
- A lookup in a dict display gives one of its values, of what was stored in it since, or the default, never a key: input that only selects the entry is no longer passed on (#239).
|
|
30
|
+
|
|
31
|
+
### XML
|
|
32
|
+
|
|
33
|
+
- `unsafe-xml` states what each parser family risks: entity amplification with Expat older than 2.7.2 for the standard library, which reads no local file; external entities in lxml before 5.0, and in `iterparse` and `ETCompatXMLParser` before 6.1 (CVE-2026-41066); `resolve_entities=True` whatever the version, `'internal'` before 5.0. A parser given `resolve_entities=False` is not reported; XML content the project wrote itself, passed to a function that parses its argument as the document, is reported at low confidence (#242).
|
|
34
|
+
|
|
35
|
+
### Analysis
|
|
36
|
+
|
|
37
|
+
- A project function's summary keeps nothing of the code constant propagation proves cannot run: no sink, returned, merged or stored value reaches its callers (#258).
|
|
38
|
+
- A `nonlocal` a nested function may leave unwritten (no write on some path, a write that cannot run, an exception before it) keeps the value the caller's variable held before the call, joined with what may be written (#258).
|
|
39
|
+
- Warnings Python raises while parsing an analysed source (an invalid escape sequence) no longer reach the analyzer's standard error (#241).
|
|
40
|
+
|
|
41
|
+
### Performance
|
|
42
|
+
|
|
43
|
+
- `-j N` (`--jobs N`) analyses independent modules in `N` processes, `0` for one per core the process may run on; the report does not depend on it (#205). Each worker prepares the project once and reads the summaries a component imports from the cache or a directory of the run's own, instead of receiving them with every task: on healthchecks, `-j 8` takes 7.9–9.2 s against 15.3–15.5 s with `-j 1` (#245).
|
|
44
|
+
|
|
45
|
+
### Documentation
|
|
46
|
+
|
|
47
|
+
- `docs/usage.md` documents the new sinks and their limits, constant allowlists, the `context` and `origin` metadata, unreachable code and `nonlocal` writes, and re-measured performance; `docs/plugins.md` documents `describe`, `PluginContext.path` and the new `Sink` fields.
|
|
48
|
+
|
|
3
49
|
## 0.22.0 (2026-10-08)
|
|
4
50
|
|
|
5
51
|
### Compatibility and migration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: Static security analysis infrastructure for Python code
|
|
5
5
|
Project-URL: Homepage, https://github.com/CoreTrace/coretrace-python-analyzer
|
|
6
6
|
Project-URL: Repository, https://github.com/CoreTrace/coretrace-python-analyzer
|
|
@@ -85,7 +85,10 @@ class SensitiveLoggingPlugin(SymbolCallDetector):
|
|
|
85
85
|
message_template: ClassVar[str] = "call to {symbol} may log sensitive data"
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
The shipped `dangerous-eval` and `weak-crypto` plugins are of this kind.
|
|
88
|
+
The shipped `dangerous-eval` and `weak-crypto` plugins are of this kind. `accepts(call, symbol, defs)`
|
|
89
|
+
keeps only some calls; `describe(ctx, call, symbol, defs)` returns the `CallReport` (message and
|
|
90
|
+
confidence) of one call, the rule's `message_template` and `confidence` by default, for a rule that
|
|
91
|
+
tells its calls apart, as `unsafe-xml` does.
|
|
89
92
|
|
|
90
93
|
### A detector of taint flows: `TaintDetector`
|
|
91
94
|
|
|
@@ -166,7 +169,7 @@ class BottleModels(ModelPlugin):
|
|
|
166
169
|
| Model | Declares |
|
|
167
170
|
|---|---|
|
|
168
171
|
| `Source(symbol, label, kinds=ALL)` | A call or attribute whose value is attacker-controlled; `label` names it in messages (`http`, `stdin`, `argv`, `http-response`). `kinds` restricts what it can inject. Text input, such as a form field, a header or a URL parameter, carries `TEXT_KINDS`, every kind but `NOSQL`: a string cannot hold a query operator. |
|
|
169
|
-
| `Sink(symbol, kinds, positions=(), keywords=())` | A call dangerous for `kinds`. `positions` limits a kind to the given argument indexes, and `keywords` to the given keyword names; a restricted kind reaches only the arguments listed there. SQL sinks read the statement only, and the SSRF sinks of HTTP clients read the URL, by position or as `url=`. A starred argument or `**kwargs` reaches no restricted kind. |
|
|
172
|
+
| `Sink(symbol, kinds, positions=(), keywords=(), selects=False, requires_called_result=NONE)` | A call dangerous for `kinds`. `positions` limits a kind to the given argument indexes, and `keywords` to the given keyword names; a restricted kind reaches only the arguments listed there. SQL sinks read the statement only, and the SSRF sinks of HTTP clients read the URL, by position or as `url=`. A starred argument or `**kwargs` reaches no restricted kind. `selects` marks an argument that chooses by name the code that runs (a module, a file, a directory, an attribute): only a check limiting it to constants proves it. `requires_called_result` lists the kinds that apply only when the call's result is itself called, as for `getattr`'s name: the engine reports them at each invocation of the result, the other kinds at the call. |
|
|
170
173
|
| `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`, including when the call is inside a project function the data goes through. |
|
|
171
174
|
| `EntryPoint(symbol, label, kinds=ALL, inputs=None)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`; or a project function or class named by its own symbol (`python.app.handlers.main`), as an integration reading a deployment declaration names a Lambda handler. `inputs` limits the parameters that are input to those positions, after `self` for a method: a handler's event, not its context. |
|
|
172
175
|
| `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
|
|
@@ -184,7 +187,9 @@ class BottleModels(ModelPlugin):
|
|
|
184
187
|
Two plugins may describe the same symbol. An identical model is registered once; two
|
|
185
188
|
`Sink` models merge their kinds, positions and keywords, so a plugin can add `SQL` to a sink
|
|
186
189
|
another plugin declared for `COMMAND`; any other difference is a conflict that stops
|
|
187
|
-
the analysis with a `ModelError` naming both plugins.
|
|
190
|
+
the analysis with a `ModelError` naming both plugins. A merged sink `selects` when either
|
|
191
|
+
does, and a kind requires the called result only when every declaration carrying it
|
|
192
|
+
does: an unconditional declaration is never weakened, whatever the order.
|
|
188
193
|
|
|
189
194
|
A plugin may also read models from the project it analyses: `project_models(self, root)`
|
|
190
195
|
returns them, from a file at `root` for instance. It is called once per directory check,
|
|
@@ -321,6 +326,10 @@ any analysis result: `SSAAnalysis` (`coretrace_python.ir.ssa`) for the code,
|
|
|
321
326
|
`TaintAnalysis` (`coretrace_python.taint`) for the flows, `RefutationAnalysis`
|
|
322
327
|
(`coretrace_python.findings.refutation`) for their verdicts, `DependencyAnalysis`
|
|
323
328
|
(`coretrace_python.dependency`) for the requirements. List what you read in `requires`.
|
|
329
|
+
`ctx.path(source_id)` is the path of a file from the root of the analysis, the one
|
|
330
|
+
`coretrace_python.findings.context` classifies (`is_test_code`, `code_context`,
|
|
331
|
+
`code_origin`): judge test code on it, never on the absolute path, which depends on where
|
|
332
|
+
the project sits. It is `None` for a file outside the root.
|
|
324
333
|
|
|
325
334
|
## Findings
|
|
326
335
|
|
|
@@ -48,7 +48,7 @@ coretrace-python-analyzer [--check | --emit-ir [--ssa]] [options] [path]
|
|
|
48
48
|
| `--plugins DIR` | Load the plugins found under `DIR` on top of the bundled ones. Repeatable. |
|
|
49
49
|
| `--no-bundled-plugins` | Do not load the plugins shipped with the package. |
|
|
50
50
|
| `--cache DIR` | Keep per-module results under `DIR` and reuse them for unchanged modules. |
|
|
51
|
-
| `--jobs N` | Analyse independent modules in `N` processes. |
|
|
51
|
+
| `-j N`, `--jobs N` | Analyse independent modules in `N` processes, `0` for one per available core. Default: `1`. |
|
|
52
52
|
| `--sbom PATH` | Write a CycloneDX bill of materials of the dependencies to `PATH`. |
|
|
53
53
|
| `--vex PATH` | Write an OpenVEX document to `PATH`: whether each advisory affecting a requirement affects the project. |
|
|
54
54
|
| `--advisories FILE` | Read a local advisory file in addition to `advisories.json` at the root. Repeatable. |
|
|
@@ -120,8 +120,8 @@ variable is not treated that way yet.
|
|
|
120
120
|
|
|
121
121
|
| Rule | Reached sink |
|
|
122
122
|
|---|---|
|
|
123
|
-
| `command-injection` | Shell or process execution (`os.system`, `subprocess` with a string, …), judged on what the input controls in the command. A string a shell runs, a string naming the program, the program itself, or an operand the program runs as a command (after the host for `ssh`, the program of `sudo`, `env`, `xargs`, `timeout`…, any argument of a batch file) is `high`. The script an interpreter runs (`bash run.sh`, `python tool.py`, its first operand when no option such as `-c` gives the code), or the module of `python -m`, is `high` and kept for review: execution is possible, and its exploitability depends on the file the input chooses. The arguments after a script or code are the script's own: options of unknown meaning, reported `medium` (a hotspot after an option of the script), refuted behind a constant prefix. Otherwise an element of an argument list run without a shell can only be read as an option: `medium`, or `high` when a documented option of the program runs a command it chooses (`git push --receive-pack=…`, `ssh -o ProxyCommand=…`). A value a documented option takes is judged by what the option does with it, whatever its prefix: run it (`sh -c`, `cmd /c`, `python -c`: `high`), keep it as data (`git commit -m`: refuted). After an option the rule does not know, the finding stays a hotspot to review. A constant prefix that keeps a standalone input from being an option (`f"v{version}"`), or an operand after `--` for a program that honours it, is refuted. Every case keeps the rule id `command-injection`, so suppressions and baselines match whatever the case. When the rule reads the command element by element and establishes what the input is there, the finding's `injection` metadata (in the JSON report, and as
|
|
124
|
-
| `code-injection` | The code `eval` or `exec` runs; the globals and locals passed with it are data. `dangerous-eval` reports every such call, this rule the ones attacker input reaches. |
|
|
123
|
+
| `command-injection` | Shell or process execution (`os.system`, `subprocess` with a string, …), judged on what the input controls in the command. A string a shell runs, a string naming the program, the program itself, or an operand the program runs as a command (after the host for `ssh`, the program of `sudo`, `env`, `xargs`, `timeout`…, any argument of a batch file) is `high`. The script an interpreter runs (`bash run.sh`, `python tool.py`, its first operand when no option such as `-c` gives the code), or the module of `python -m`, is `high` and kept for review: execution is possible, and its exploitability depends on the file the input chooses. The arguments after a script or code are the script's own: options of unknown meaning, reported `medium` (a hotspot after an option of the script), refuted behind a constant prefix. Otherwise an element of an argument list run without a shell can only be read as an option: `medium`, or `high` when a documented option of the program runs a command it chooses (`git push --receive-pack=…`, `ssh -o ProxyCommand=…`). A value a documented option takes is judged by what the option does with it, whatever its prefix: run it (`sh -c`, `cmd /c`, `python -c`: `high`), keep it as data (`git commit -m`: refuted). After an option the rule does not know, the finding stays a hotspot to review. A constant prefix that keeps a standalone input from being an option (`f"v{version}"`), or an operand after `--` for a program that honours it, is refuted. Every case keeps the rule id `command-injection`, so suppressions and baselines match whatever the case. When the rule reads the command element by element and establishes what the input is there, the finding's `injection` metadata (in the JSON report, and as a property of its SARIF result) says so: `command` when the input runs or chooses what runs (a shell string, the program, an operand run as a command, the value of an option that runs it); `option` when it can only reach the program as an option or as the value of an option not established to run it, and the message is then titled `Option injection`. When that cannot be established (an option whose effect is to review, an unknown program or element before the input, a command that is not a list or tuple display, a sink such as `os.system` the rule does not read element by element), the metadata is absent and the title stays `Command injection`. |
|
|
124
|
+
| `code-injection` | The code `eval` or `exec` runs; the globals and locals passed with it are data. `dangerous-eval` reports every such call, this rule the ones attacker input reaches. Also the module `importlib.import_module`, `__import__` or `runpy.run_module` imports, and the file `runpy.run_path` runs, by position or by keyword (`name`, `mod_name`, `path_name`); the package of a relative name, the globals and the run name are data. An import runs the top-level code of a module the input chooses among those that can be found, a script path the file it chooses: execution is possible, and how far it goes depends on what the input can reach, so these findings are titled `Untrusted module import` and `Untrusted script execution`. A constant package prefix (`"app.plugins." + name`) confines an import to that package: a hotspot. The environment and the command line reach these sinks as they reach `eval`. Also the directory `sys.path.insert` (its second argument), `sys.path.append` and `sys.path.extend` add to the import path, and the directory `site.addsitedir` adds (`sitedir`), judged the same way. A directory on the import path runs code only when a later import resolves to a module the attacker placed there: a hotspot, titled `Untrusted import path`. `site.addsitedir` runs the import lines of the directory's `.pth` files during the call: a vulnerability, titled `Untrusted site directory`, exploitable when the directory holds files the attacker controls, as the finding's evidence states. Coverage of the import path is partial: writes to `sys.path` that are not calls, a slice assignment (`sys.path[:0] = [d]`), `sys.path += [d]` or rebinding `sys.path`, are not reported. Also the name `getattr(obj, name)` receives, when the attribute it selects is called: the finding, titled `Untrusted attribute call`, is placed at each call of the result, one per call, with the lines of the selection and of the call in its metadata (`selected_line`, `invoked_line`), and through project functions at the call that reaches them. What runs depends on what the object exposes, so it is a hotspot, on a module too and behind a constant prefix; a check proves the name only when it limits the value `getattr` received to constants, where `getattr` selects (even past a join) or before the call, and a check of a name rebound since proves nothing; a check before the call does not prove a callable selected in an earlier iteration of a loop, which reaches the call across the loop's back edge: only a check where it was selected does (even when the name never changes, which this version does not establish); nothing is reported when the selection or the call cannot run, in the function and inside a project function. Calling the result of a call, or an item of it, calls what the call returned: its arguments are not read as the call's, so `getattr(self, 'factory')(None, data)` or `eval('lambda x: x')(data)` reports nothing. A `getattr` whose result is only read is not reported, nor, in this version, one whose result is returned to the caller, stored in an attribute or a container, or passed on before it is called, nor `operator.attrgetter`, `operator.methodcaller`, `obj.__getattribute__(name)` or `vars(obj)[name]`; checks made inside a project function are not seen from its callers, as for every sink. |
|
|
125
125
|
| `sql-injection` | A database statement (`cursor.execute`, SQLAlchemy `text`, Django `raw`, …). Parameters of a parameterised query are not statements. |
|
|
126
126
|
| `nosql-injection` | A NoSQL query, such as a MongoDB filter, given attacker input that may hold a structure: a JSON body giving `{"$ne": null}` matches any value. The input that may hold a structure is JSON, the raw bodies a `json.loads` decodes, files, WebSocket messages and standard input. Text cannot hold an operator: form fields, query parameters, headers, cookies, URL parameters, the command line, the environment, and an entry-point parameter annotated with scalars (`str`, `int`, `Optional[str]`, `Annotated[str, Query()]`, `list[str]`, …), which FastAPI validates. A Django, REST framework, aiohttp or tornado view reads a request object: its text attributes (`GET`, `POST`, `headers`, `query_params`, `query`, `match_info`, `arguments`, …) and the URL parameters after it are text, its body, files and `data` are not. Limits: a helper function the request is passed to reads it as a whole; the keys of a dict are not followed, so a string concatenated into `$where` is not reported. The analyzer ships no NoSQL sink: model plugins declare them. |
|
|
127
127
|
| `path-traversal` | A file system path (`open`, `send_file`, `os.remove`, …). |
|
|
@@ -132,10 +132,19 @@ variable is not treated that way yet.
|
|
|
132
132
|
| `plaintext-credential-storage` | A parameter named like a password stored in a database without hashing. Medium confidence, since a name is a hint. |
|
|
133
133
|
| `exploitable-vulnerability` | An API affected by an advisory of a vulnerable requirement, see Dependencies. |
|
|
134
134
|
|
|
135
|
-
Each flow is judged before it is reported.
|
|
136
|
-
|
|
135
|
+
Each flow is judged before it is reported. Code constant propagation proves can never
|
|
136
|
+
run (under `if False:`, `while False:`) reports nothing, in the function and through the
|
|
137
|
+
project functions that contain it: their summaries keep nothing of such code, neither a
|
|
138
|
+
sink nor a returned, merged or stored value. Code that may run is kept, whatever decides
|
|
139
|
+
it at run time, a parameter or a module-level name. A dominating guard that proves the value
|
|
140
|
+
safe (`isdigit()`, membership in a constant allowlist, equality with a constant, a
|
|
137
141
|
numeric value proven by `int()`, `len()` or bounded arithmetic, a validator such as
|
|
138
|
-
`re.fullmatch`) refutes it and nothing is reported. A
|
|
142
|
+
`re.fullmatch`) refutes it and nothing is reported. A sink that selects code by name,
|
|
143
|
+
the module of `importlib.import_module`, `__import__` or `runpy.run_module` and the file
|
|
144
|
+
of `runpy.run_path`, is proven only by a check that limits the value to constants:
|
|
145
|
+
membership in a constant allowlist or equality with a constant. A check of its shape
|
|
146
|
+
(`isidentifier()`, `isalnum()`, `re.fullmatch`) limits its characters, not the modules
|
|
147
|
+
or files it can name, and leaves a hotspot. A hotspot is reported at medium
|
|
139
148
|
confidence. Two things make a flow a hotspot:
|
|
140
149
|
|
|
141
150
|
- A guard that examines the data without proving it safe. That covers a test of the value
|
|
@@ -149,6 +158,28 @@ a vulnerability. When the flow passes the object whole, to the sink or to a func
|
|
|
149
158
|
a guard on any of its attributes still counts. The verdict and its evidence are in the
|
|
150
159
|
finding's metadata.
|
|
151
160
|
|
|
161
|
+
A membership test (`in`, `not in`) is a constant allowlist when every member is a constant
|
|
162
|
+
that cannot change before the test:
|
|
163
|
+
|
|
164
|
+
- a tuple, list or set display written in the function, or a dict display by its keys,
|
|
165
|
+
tested directly or through `.keys()`;
|
|
166
|
+
- `frozenset`, `set`, `tuple` or `list` of one of these;
|
|
167
|
+
- a module-level tuple, or `frozenset` of a display, of constants, bound once in its
|
|
168
|
+
module and never rebound there, by another assignment, an augmented assignment or a
|
|
169
|
+
`global` statement.
|
|
170
|
+
|
|
171
|
+
A list, set or dict of the function, a display or the `set` or `list` of one, counts only
|
|
172
|
+
while the function only reads it: changed by a method, stored, or passed to a call before
|
|
173
|
+
the test, it may hold anything. A
|
|
174
|
+
module-level list, set or dict can be changed from any module, so it never counts, and
|
|
175
|
+
the guard leaves a hotspot. Rebinding a module-level name from another module
|
|
176
|
+
(`settings.ALLOWED = ...`) is not seen, as for the module-level strings that fix a URL.
|
|
177
|
+
|
|
178
|
+
A lookup in a dict display, `{"list": "ls"}[key]` or `.get(key, default)`, gives one of
|
|
179
|
+
its values, of what was stored in it since, or the default, never a key: input that only
|
|
180
|
+
selects the entry, or that is itself a key of the display, is not passed on. The same lookup in a module-level dict passes the key on, as any other
|
|
181
|
+
subscript does.
|
|
182
|
+
|
|
152
183
|
Django's `render_to_string(name, context)` returns HTML in which autoescaping escaped
|
|
153
184
|
every variable, so data reaching a response through it is no `xss`, if the template really
|
|
154
185
|
escapes. On a directory check, the analyzer reads the project's templates to decide it.
|
|
@@ -181,7 +212,7 @@ reads no template.
|
|
|
181
212
|
| `debug-enabled` | `app.run(debug=True)` on a Flask application. |
|
|
182
213
|
| `missing-timeout` | A Requests or httpx call without a `timeout`. |
|
|
183
214
|
| `insecure-tls` | `verify=False` on a Requests or httpx call, or `ssl._create_unverified_context()`. |
|
|
184
|
-
| `unsafe-xml` |
|
|
215
|
+
| `unsafe-xml` | An XML parser of the standard library or of lxml, reported for what it risks; `defusedxml` is the safe replacement. The standard library's parsers run on Expat, which reads no local file but expands the entities a document declares: a crafted document can exhaust memory with Expat older than 2.7.2, which depends on the Python build at run time (`pyexpat.EXPAT_VERSION`). lxml resolves external entities by default before 5.0, and in `iterparse` and `ETCompatXMLParser` before 6.1 (CVE-2026-41066), which lets a crafted document read local files; `resolve_entities=True` does so whatever the version, and `'internal'` keeps external entities out only from lxml 5.0 on, an earlier lxml reading the string as true; a parser given `resolve_entities=False` is not reported. XML content the project wrote itself, a string constant or a module-level string bound once passed to a function that parses its argument as the document (`fromstring`, `parseString`, `XML`), is reported at low confidence; a constant path keeps its confidence, since whoever can write the file chooses the document. |
|
|
185
216
|
| `unsafe-archive-extraction` | `tarfile` or `zipfile` extraction, or `shutil.unpack_archive`, without a member `filter`. |
|
|
186
217
|
| `insecure-temp-file` | `tempfile.mktemp`, which names a file without creating it. |
|
|
187
218
|
| `weak-random` | A `random` function whose result is assigned to a credential-like name, or produced by a function named like one. Medium confidence. |
|
|
@@ -194,7 +225,7 @@ sources and in configuration files.
|
|
|
194
225
|
| Rule | Confidence | Trigger |
|
|
195
226
|
|---|---|---|
|
|
196
227
|
| `hardcoded-secret` | high | A provider-specific format: AWS, GitHub, Slack, Stripe, Google, private keys, JWTs, SendGrid, Twilio. |
|
|
197
|
-
| `hardcoded-credential` | medium | A credential-like name (`password`, `token`, `api_key`, `app.config['SECRET_KEY']`, …) bound to a real value. Placeholders are excluded. Low confidence in test code, fixtures and template files such as `.env.example`, with the context in the metadata. |
|
|
228
|
+
| `hardcoded-credential` | medium | A credential-like name (`password`, `token`, `api_key`, `app.config['SECRET_KEY']`, …) bound to a real value. Placeholders are excluded. Low confidence in test code, fixtures and template files such as `.env.example`, with the context in the metadata, judged on the file's path in the project, not on where the project sits. |
|
|
198
229
|
| `high-entropy-string` | low | An opaque high-entropy token. Hex digests (MD5, SHA-1, SHA-256, SHA-512 lengths, or a name such as `hash`, `checksum`, `commit`), subresource-integrity hashes (`sha512-…`) and character-set constants are not secrets unless a credential name says so. |
|
|
199
230
|
|
|
200
231
|
### Dependencies
|
|
@@ -303,7 +334,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
303
334
|
```json
|
|
304
335
|
{
|
|
305
336
|
"schema_version": 1,
|
|
306
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
337
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.23.0"},
|
|
307
338
|
"root": "/home/me/project",
|
|
308
339
|
"findings": [
|
|
309
340
|
{
|
|
@@ -334,6 +365,25 @@ in the file (`"/tool/app/api_token"`). The SARIF log gives such a result no `reg
|
|
|
334
365
|
with the pointer under the location's `properties`, and the text report shows the file
|
|
335
366
|
alone (`settings.toml: high hardcoded-credential: …`).
|
|
336
367
|
|
|
368
|
+
A finding in test code carries `context: test` in its metadata, in the JSON report and as
|
|
369
|
+
a property of its SARIF result. Test code is a file under a `test`, `tests`, `testing`,
|
|
370
|
+
`fixtures` or `__tests__` directory of the project, or a file named `test_*.py`,
|
|
371
|
+
`*_test.py` or `conftest.py`, judged on its path from the root of the report, not on
|
|
372
|
+
where the project sits. For a file checked alone, that root is the file's own directory,
|
|
373
|
+
as the report gives it, so a `test_*.py` module is test code either way, while a test
|
|
374
|
+
directory above the file is seen only when its directory is checked. The context
|
|
375
|
+
changes no verdict: it is there for baselines, `--fail-on`
|
|
376
|
+
wrappers, readers and refiner plugins, which see it. A rule may still give its own
|
|
377
|
+
context, as `hardcoded-credential` does with `example` for template files, and the
|
|
378
|
+
SARIF result carries that context too.
|
|
379
|
+
|
|
380
|
+
A finding in vendored code, under a `third_party`, `third-party`, `vendor` or `vendored`
|
|
381
|
+
directory, carries `origin: vendored`, in the JSON report and as a property of its SARIF
|
|
382
|
+
result. The origin is told apart from the context: the tests of a vendored project
|
|
383
|
+
carry both `context: test` and `origin: vendored`. Vendored code is still analysed and
|
|
384
|
+
its findings reported, so that a flow from the project through a vendored module is
|
|
385
|
+
not lost; the origin changes no verdict either.
|
|
386
|
+
|
|
337
387
|
`root` is the directory the paths are relative to. A file's `status` is `analysed`,
|
|
338
388
|
`ambiguous` (analysed, but another file has its module name, see above), `syntax-error`
|
|
339
389
|
or `unreadable`; only `analysed` files count in `files_analysed`.
|
|
@@ -348,7 +398,7 @@ bytes. A single-file check lists no components.
|
|
|
348
398
|
|
|
349
399
|
```json
|
|
350
400
|
"tool": {
|
|
351
|
-
"name": "coretrace-python-analyzer", "version": "0.
|
|
401
|
+
"name": "coretrace-python-analyzer", "version": "0.23.0",
|
|
352
402
|
"components": [
|
|
353
403
|
{"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
|
|
354
404
|
{"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
|
|
@@ -718,7 +768,7 @@ project's behalf would go unseen. Without a lock file, a package is never
|
|
|
718
768
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
719
769
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
720
770
|
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
721
|
-
(`coretrace-python-analyzer 0.
|
|
771
|
+
(`coretrace-python-analyzer 0.23.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
722
772
|
`@id` changes with the advisory data a statement was decided with.
|
|
723
773
|
|
|
724
774
|
## Large projects
|
|
@@ -730,8 +780,15 @@ graph and the modules it imports. On the next run an unchanged
|
|
|
730
780
|
module is served from the cache, so editing one file re-analyses that file and its
|
|
731
781
|
importers only. Entries are plain data; an unreadable entry is recomputed.
|
|
732
782
|
|
|
733
|
-
`--jobs N` analyses independent modules in `N` processes
|
|
734
|
-
|
|
783
|
+
`--jobs N` (`-j N`) analyses independent modules in `N` processes; `0` takes one per core
|
|
784
|
+
the process may run on, as `runtime.jobs` of `ctrace` does (its CPU affinity, read from
|
|
785
|
+
Python 3.13 on, and on Linux before; the cores of the machine otherwise), and the default
|
|
786
|
+
is one. On Windows, a pool holds at most 61 processes, the limit of Python's process pool
|
|
787
|
+
there, whatever `N`.
|
|
788
|
+
Modules are scheduled imports first, so the result is the same whatever `N`: the report,
|
|
789
|
+
the order of its findings, `--fail-on`, the cache, the baseline and the SBOM and VEX
|
|
790
|
+
documents. The command always waits for the complete report. There is no `--async`
|
|
791
|
+
option: `ctrace` deprecated its own for `-j`/`--jobs`, which this follows.
|
|
735
792
|
|
|
736
793
|
```bash
|
|
737
794
|
coretrace-python-analyzer --check src/ --cache .coretrace --jobs 4
|
|
@@ -750,11 +807,24 @@ Measured on one core of a laptop, single process, cold start, with the bundled p
|
|
|
750
807
|
|
|
751
808
|
Time grows a little faster than linearly with the size of the project, because the
|
|
752
809
|
interprocedural summaries are iterated to a fixpoint over the module graph. A warm
|
|
753
|
-
`--cache` brings an unchanged healthchecks to about one second
|
|
754
|
-
quarter of the cold time at the cost of one process's memory per job. The
|
|
810
|
+
`--cache` brings an unchanged healthchecks to about one second. The
|
|
755
811
|
`healthchecks` repository is part of the regression corpus, so the analysis time of a
|
|
756
812
|
real 45 000-line project is exercised on every change.
|
|
757
813
|
|
|
814
|
+
`--jobs` pays when a wave of the module graph holds many components: each process
|
|
815
|
+
prepares the project once and reads the summaries of the modules a component imports
|
|
816
|
+
from a directory of finished modules, the cache or one of the run's own, at the cost of
|
|
817
|
+
one process's memory per job. Measured on a laptop with 8 cores (4 performance, 4
|
|
818
|
+
efficiency), macOS, Python 3.14, bundled plugins, cold start (October 2026):
|
|
819
|
+
|
|
820
|
+
| Project | Python files | `-j 1` | `-j 8` |
|
|
821
|
+
|---|---:|---:|---:|
|
|
822
|
+
| healthchecks | 653 | 15.3–15.5 s | 7.9–9.2 s |
|
|
823
|
+
| mitmproxy | 583 | 51.6 s | 45.6 s |
|
|
824
|
+
|
|
825
|
+
The modules of a strongly connected import cycle are analysed together by one process,
|
|
826
|
+
and a wave waits for its slowest component: a project whose cycles are large gains less.
|
|
827
|
+
|
|
758
828
|
## Plugins
|
|
759
829
|
|
|
760
830
|
The package ships 35 plugins, loaded by default. Security models for the standard library
|
|
@@ -847,7 +917,10 @@ such as `metaclass=`, `with` targets of any assignable form and `match` with eve
|
|
|
847
917
|
pattern kind. `finally` is modelled on the normal path only. A
|
|
848
918
|
`nonlocal` write made by a nested function flows back to the enclosing variable when the
|
|
849
919
|
enclosing function calls that nested function directly by name; a nested function passed
|
|
850
|
-
around and called elsewhere keeps its writes to itself.
|
|
920
|
+
around and called elsewhere keeps its writes to itself. Where the nested function may
|
|
921
|
+
return without writing the name (no write on some path, a write constant propagation
|
|
922
|
+
proves cannot run, or an exception before it), the variable keeps the value it had
|
|
923
|
+
before the call, joined with what may be written; only a write on every path replaces it. Methods of module-level classes are
|
|
851
924
|
analysed like functions.
|
|
852
925
|
|
|
853
926
|
## Looking at the intermediate representation
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Module-level names whose value is known (architecture §18): the names bound once and
|
|
2
|
+
never rebound, and among them the ones bound to an immutable collection of constants,
|
|
3
|
+
which an allowlist reads."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
12
|
+
from coretrace_python.cfg import CFGError
|
|
13
|
+
from coretrace_python.hir import nodes
|
|
14
|
+
from coretrace_python.ir.lowering import (
|
|
15
|
+
MODULE_BODY,
|
|
16
|
+
LoweringError,
|
|
17
|
+
PyIRAnalysis,
|
|
18
|
+
analyzable_functions,
|
|
19
|
+
)
|
|
20
|
+
from coretrace_python.ir.model import (
|
|
21
|
+
BuildDict,
|
|
22
|
+
BuildList,
|
|
23
|
+
BuildSet,
|
|
24
|
+
BuildTuple,
|
|
25
|
+
Call,
|
|
26
|
+
Constant,
|
|
27
|
+
ForNext,
|
|
28
|
+
Instruction,
|
|
29
|
+
SetGlobal,
|
|
30
|
+
StoreLocal,
|
|
31
|
+
Symbol,
|
|
32
|
+
Value,
|
|
33
|
+
)
|
|
34
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
35
|
+
|
|
36
|
+
FROZENSET = SymbolId("python.builtins.frozenset")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def module_bindings(ctx: AnalysisContext) -> tuple[Mapping[str, Instruction], Mapping[Value, Instruction]]:
|
|
40
|
+
"""The module-level names bound once and never rebound, by no other statement of the
|
|
41
|
+
module body, no function assigning them as ``global``, no definition of that name,
|
|
42
|
+
each with the instruction that makes its value; and the definitions of the module
|
|
43
|
+
body's values. A module that does not lower entirely has none."""
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
functions = [ctx.get(PyIRAnalysis, function) for function in analyzable_functions(ctx.module)]
|
|
47
|
+
except (LoweringError, CFGError):
|
|
48
|
+
return MappingProxyType({}), MappingProxyType({})
|
|
49
|
+
body = next((function for function in functions if function.name == MODULE_BODY), None)
|
|
50
|
+
if body is None:
|
|
51
|
+
return MappingProxyType({}), MappingProxyType({})
|
|
52
|
+
defs = {i.result: i for block in body.blocks for i in block.instructions if i.result is not None}
|
|
53
|
+
stored: dict[str, list[Value | None]] = {}
|
|
54
|
+
for block in body.blocks:
|
|
55
|
+
for instruction in block.instructions:
|
|
56
|
+
if isinstance(instruction, StoreLocal):
|
|
57
|
+
stored.setdefault(instruction.name, []).append(instruction.value)
|
|
58
|
+
if isinstance(block.terminator, ForNext):
|
|
59
|
+
# A ``for`` binds its target to each item.
|
|
60
|
+
stored.setdefault(block.terminator.target, []).append(None)
|
|
61
|
+
rebound = {
|
|
62
|
+
i.name for function in functions for block in function.blocks for i in block.instructions
|
|
63
|
+
if isinstance(i, SetGlobal)
|
|
64
|
+
}
|
|
65
|
+
rebound |= {s.name for s in ctx.module.body if isinstance(s, nodes.Function | nodes.Class)}
|
|
66
|
+
bound: dict[str, Instruction] = {}
|
|
67
|
+
for name, values in stored.items():
|
|
68
|
+
value = values[0] if len(values) == 1 and name not in rebound else None
|
|
69
|
+
made = defs.get(value) if value is not None else None
|
|
70
|
+
if made is not None:
|
|
71
|
+
bound[name] = made
|
|
72
|
+
return MappingProxyType(bound), MappingProxyType(defs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def constant_members(display: Instruction | None, defs: Mapping[Value, Instruction]) -> bool:
|
|
76
|
+
"""Whether ``display`` is a tuple, list or set display whose items are all constants,
|
|
77
|
+
or a dict display whose keys are, with nothing unpacked into it: the members a
|
|
78
|
+
membership test reads."""
|
|
79
|
+
|
|
80
|
+
if isinstance(display, BuildList | BuildTuple | BuildSet):
|
|
81
|
+
return not display.unpacked and all(isinstance(defs.get(e), Constant) for e in display.elements)
|
|
82
|
+
if isinstance(display, BuildDict):
|
|
83
|
+
return not display.unpacked and all(isinstance(defs.get(key), Constant) for key, _ in display.items)
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def converts(call: Call, defs: Mapping[Value, Instruction], symbols: frozenset[SymbolId]) -> Value | None:
|
|
88
|
+
"""The one positional argument of ``call`` when it calls one of ``symbols`` with
|
|
89
|
+
nothing else (``frozenset(x)``), otherwise ``None``."""
|
|
90
|
+
|
|
91
|
+
callee = defs.get(call.callee)
|
|
92
|
+
if not isinstance(callee, Symbol) or callee.symbol_id not in symbols:
|
|
93
|
+
return None
|
|
94
|
+
if len(call.arguments) != 1 or call.keywords or call.starred:
|
|
95
|
+
return None
|
|
96
|
+
return call.arguments[0]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ModuleCollectionsAnalysis(Analysis[frozenset[str]]):
|
|
100
|
+
"""The module-level names bound once and never rebound (``module_bindings``) to an
|
|
101
|
+
immutable collection of constants: a tuple display, or ``frozenset`` of a display
|
|
102
|
+
whose members are constants (``constant_members``). A list, a set or a dict can still
|
|
103
|
+
change after it is bound, so it is not one."""
|
|
104
|
+
|
|
105
|
+
name: ClassVar[str] = "abstract.module_collections"
|
|
106
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({PyIRAnalysis})
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def compute(cls, ctx: AnalysisContext) -> frozenset[str]:
|
|
110
|
+
bound, defs = module_bindings(ctx)
|
|
111
|
+
found: set[str] = set()
|
|
112
|
+
for name, made in bound.items():
|
|
113
|
+
collection: Instruction | None = made
|
|
114
|
+
if isinstance(made, Call):
|
|
115
|
+
argument = converts(made, defs, frozenset({FROZENSET}))
|
|
116
|
+
collection = defs.get(argument) if argument is not None else None
|
|
117
|
+
elif not isinstance(made, BuildTuple):
|
|
118
|
+
continue
|
|
119
|
+
if constant_members(collection, defs):
|
|
120
|
+
found.add(name)
|
|
121
|
+
return frozenset(found)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""The constant text a string starts with (architecture §18): what ``+`` and f-strings
|
|
2
|
+
build from string literals and from module-level names bound once to one. Anything
|
|
3
|
+
else, a parameter, a call result, an import, ``%`` or ``format``, is text of unknown
|
|
4
|
+
value."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from types import MappingProxyType
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from coretrace_python.abstract.module_constants import module_bindings
|
|
13
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
14
|
+
from coretrace_python.ir.lowering import PyIRAnalysis
|
|
15
|
+
from coretrace_python.ir.model import (
|
|
16
|
+
BinaryOp,
|
|
17
|
+
BuildString,
|
|
18
|
+
Constant,
|
|
19
|
+
Global,
|
|
20
|
+
Instruction,
|
|
21
|
+
Value,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def leading_text(value: Value, defs: Mapping[Value, Instruction], strings: Mapping[str, str]) -> tuple[str, bool]:
|
|
26
|
+
"""The constant text ``value`` starts with, and whether that text is all of it, given
|
|
27
|
+
the definitions of the function's values and the module's ``strings``."""
|
|
28
|
+
|
|
29
|
+
made = defs.get(value)
|
|
30
|
+
if isinstance(made, Constant):
|
|
31
|
+
return (made.value, True) if isinstance(made.value, str) else ("", False)
|
|
32
|
+
if isinstance(made, Global):
|
|
33
|
+
known = strings.get(made.name)
|
|
34
|
+
return ("", False) if known is None else (known, True)
|
|
35
|
+
if isinstance(made, BinaryOp) and made.operator == "add":
|
|
36
|
+
left, whole = leading_text(made.left, defs, strings)
|
|
37
|
+
if not whole:
|
|
38
|
+
return left, False
|
|
39
|
+
right, whole = leading_text(made.right, defs, strings)
|
|
40
|
+
return left + right, whole
|
|
41
|
+
if isinstance(made, BuildString):
|
|
42
|
+
text = ""
|
|
43
|
+
for part in made.parts:
|
|
44
|
+
piece, whole = leading_text(part, defs, strings)
|
|
45
|
+
text += piece
|
|
46
|
+
if not whole:
|
|
47
|
+
return text, False
|
|
48
|
+
return text, True
|
|
49
|
+
return "", False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ModuleStringsAnalysis(Analysis[Mapping[str, str]]):
|
|
53
|
+
"""The module-level names bound once and never rebound (``module_bindings``) to a
|
|
54
|
+
string literal, with that literal."""
|
|
55
|
+
|
|
56
|
+
name: ClassVar[str] = "abstract.module_strings"
|
|
57
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({PyIRAnalysis})
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def compute(cls, ctx: AnalysisContext) -> Mapping[str, str]:
|
|
61
|
+
bound, _ = module_bindings(ctx)
|
|
62
|
+
return MappingProxyType(
|
|
63
|
+
{
|
|
64
|
+
name: made.value
|
|
65
|
+
for name, made in bound.items()
|
|
66
|
+
if isinstance(made, Constant) and isinstance(made.value, str)
|
|
67
|
+
}
|
|
68
|
+
)
|
|
@@ -62,6 +62,33 @@ class PythonStdlibModels(ModelPlugin):
|
|
|
62
62
|
# The code is the first argument; the globals and locals it runs with are data.
|
|
63
63
|
Sink(_sym("builtins.eval"), TaintKind.CODE, ((TaintKind.CODE, (0,)),)),
|
|
64
64
|
Sink(_sym("builtins.exec"), TaintKind.CODE, ((TaintKind.CODE, (0,)),)),
|
|
65
|
+
# The module or the file the call imports or runs; its globals, its run name and
|
|
66
|
+
# the package of a relative name are data (#240).
|
|
67
|
+
*(
|
|
68
|
+
Sink(_sym(symbol), TaintKind.CODE, ((TaintKind.CODE, (0,)),), ((TaintKind.CODE, (keyword,)),), selects=True)
|
|
69
|
+
for symbol, keyword in (
|
|
70
|
+
("importlib.import_module", "name"),
|
|
71
|
+
("builtins.__import__", "name"),
|
|
72
|
+
("importlib.__import__", "name"),
|
|
73
|
+
("runpy.run_module", "mod_name"),
|
|
74
|
+
("runpy.run_path", "path_name"),
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
# The directory imports are looked up in, and the site directory whose ``.pth``
|
|
78
|
+
# files run; the index of ``insert`` is data (#240).
|
|
79
|
+
Sink(_sym("sys.path.insert"), TaintKind.CODE, ((TaintKind.CODE, (1,)),), selects=True),
|
|
80
|
+
Sink(_sym("sys.path.append"), TaintKind.CODE, ((TaintKind.CODE, (0,)),), selects=True),
|
|
81
|
+
Sink(_sym("sys.path.extend"), TaintKind.CODE, ((TaintKind.CODE, (0,)),), selects=True),
|
|
82
|
+
Sink(_sym("site.addsitedir"), TaintKind.CODE, ((TaintKind.CODE, (0,)),), ((TaintKind.CODE, ("sitedir",)),), selects=True),
|
|
83
|
+
# The name of the attribute ``getattr`` selects, when what it selects is called;
|
|
84
|
+
# the object and the default are data (#240).
|
|
85
|
+
Sink(
|
|
86
|
+
_sym("builtins.getattr"),
|
|
87
|
+
TaintKind.CODE,
|
|
88
|
+
((TaintKind.CODE, (1,)),),
|
|
89
|
+
selects=True,
|
|
90
|
+
requires_called_result=TaintKind.CODE,
|
|
91
|
+
),
|
|
65
92
|
Sink(_sym("builtins.open"), TaintKind.PATH),
|
|
66
93
|
Sink(_sym("os.remove"), TaintKind.PATH),
|
|
67
94
|
Sink(_sym("os.unlink"), TaintKind.PATH),
|
|
@@ -9,6 +9,7 @@ from collections.abc import Sequence
|
|
|
9
9
|
from typing import ClassVar
|
|
10
10
|
|
|
11
11
|
from coretrace_python.findings import Finding
|
|
12
|
+
from coretrace_python.findings.context import path_in_project
|
|
12
13
|
from coretrace_python.plugins import PluginContext, ProjectContext, ProjectPlugin
|
|
13
14
|
from coretrace_python.plugins.secrets import (
|
|
14
15
|
DEFAULT_CREDENTIAL_NAMES,
|
|
@@ -32,7 +33,7 @@ class ConfigSecrets(SecretDetector, ProjectPlugin):
|
|
|
32
33
|
return ()
|
|
33
34
|
findings: list[Finding] = []
|
|
34
35
|
for value, name, span, function in config_literals(ctx.root):
|
|
35
|
-
finding = self.judge(value, name, span, function)
|
|
36
|
+
finding = self.judge(value, name, span, function, path=path_in_project(span.source_id, ctx.root))
|
|
36
37
|
if finding is not None:
|
|
37
38
|
findings.append(finding)
|
|
38
39
|
return findings
|