coretrace-python-analyzer 0.7.0__tar.gz → 0.9.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.7.0 → coretrace_python_analyzer-0.9.0}/CHANGELOG.md +18 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/plugins.md +19 -2
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/usage.md +19 -2
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cache.py +16 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cli.py +2 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/__init__.py +2 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/advisories.py +36 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/correlation.py +4 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/graph.py +34 -1
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/engine.py +21 -6
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/refutation.py +41 -15
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/__init__.py +8 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/summaries.py +131 -28
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/api.py +8 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/engine.py +35 -28
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/models.py +12 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/healthchecks.json +0 -56
- coretrace_python_analyzer-0.9.0/tests/test_attacker_arguments.py +203 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cache.py +3 -0
- coretrace_python_analyzer-0.9.0/tests/test_project_models.py +117 -0
- coretrace_python_analyzer-0.9.0/tests/test_project_validators.py +93 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.9.0/tests/test_summary_sanitizers.py +136 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/README.md +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Precision
|
|
6
|
+
|
|
7
|
+
- An advisory entry point may name the `attacker_arguments` through which attacker input exploits it: by keyword, and by position when the argument may be passed positionally, a variadic one taking every later position. Attacker input in another argument leaves the call reachable instead of exploitable, so an advisory for `send_from_directory` that names its `path` no longer counts `download_name=`, and one for `requests.get` that names its `url` no longer counts the body. An argument unpacked with `*` or `**` may fill any of them: like an argument condition it leaves undecided, it does not rule the call out, so a wrapper forwarding `*args, **kwargs` still reaches the entry point. An entry point naming no argument counts every argument, as before. Each taint flow now records how its value is passed to the sink (#145).
|
|
8
|
+
|
|
9
|
+
## 0.8.0 (2026-09-25)
|
|
10
|
+
|
|
11
|
+
### Precision
|
|
12
|
+
|
|
13
|
+
- A `Validator` proves its argument safe only for the kinds it declares. A validator declared for `REDIRECT` used to refute a command injection through the same value; the flow is now a hotspot, and it is refuted only when the validator covers every kind reaching the sink (#143).
|
|
14
|
+
- A sanitizer called inside a project function protects the flows through that function, for every caller. That covers a helper that escapes before answering, a view redirecting to `reverse(...)`, and a helper rendering a template the analyzer shows escaping. Function summaries keep, for each parameter, the taint kinds cleared on every path from it to an external call or to the return value. A path that skips the sanitizer, or a kind it does not clear, still reaches the sink. The cache format is bumped (#134).
|
|
15
|
+
|
|
16
|
+
### Plugins
|
|
17
|
+
|
|
18
|
+
- `Plugin.project_models(root)` lets a plugin read models from the project under analysis, such as validators the project declares in a file. It is called once per directory check, in every process, and its models are part of the cache key. A model declared wrongly raises `ModelError`, which the command line now reports as an error (exit status 2) instead of a traceback (#140).
|
|
19
|
+
- A `Validator` model may name a function of the project by its project symbol (`python.hc.accounts.views._allow_redirect`), and the refutation recognises it in its own module too, where a call to it has no imported symbol. A project can thus declare its own validation helpers, and a flow they guard is refuted (#134).
|
|
20
|
+
|
|
3
21
|
## 0.7.0 (2026-09-25)
|
|
4
22
|
|
|
5
23
|
### Precision
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -142,7 +142,7 @@ class BottleModels(ModelPlugin):
|
|
|
142
142
|
|---|---|
|
|
143
143
|
| `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. |
|
|
144
144
|
| `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. |
|
|
145
|
-
| `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds
|
|
145
|
+
| `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`, including when the call is inside a project function the data goes through. |
|
|
146
146
|
| `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
|
|
147
147
|
| `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
|
|
148
148
|
| `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
|
|
@@ -150,7 +150,7 @@ class BottleModels(ModelPlugin):
|
|
|
150
150
|
| `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
|
|
151
151
|
| `SafeArgument(symbol, argument, values, position=None, kinds=ALL)` | A call to the sink `symbol` whose `argument` (by keyword, or at `position`) denotes one of `values` is not a sink for `kinds`, such as `yaml.load` with `Loader=yaml.SafeLoader`. Values are written as the analyzer records arguments: a symbol by its canonical name, a constant as Python writes it (`True`). Only a value given explicitly makes the call safe. |
|
|
152
152
|
| `TemplateRender(symbol, position=0, keyword="template_name")` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string`. What it returns carries no `HTML` when the name is written in the call and the project's template escapes everything it prints (see the usage guide). |
|
|
153
|
-
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe
|
|
153
|
+
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe for `kinds`, such as `re.fullmatch`. A flow guarded by it is refuted when it carries no other kind into the sink, and is a hotspot otherwise: a redirect validator does not make a shell command safe. `symbol` may be a function of the project, by its project symbol (`python.hc.accounts.views._allow_redirect`), which then counts wherever it is called, in its own module too. |
|
|
154
154
|
| `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
|
|
155
155
|
|
|
156
156
|
Two plugins may describe the same symbol. An identical model is registered once; two
|
|
@@ -158,6 +158,23 @@ Two plugins may describe the same symbol. An identical model is registered once;
|
|
|
158
158
|
another plugin declared for `COMMAND`; any other difference is a conflict that stops
|
|
159
159
|
the analysis with a `ModelError` naming both plugins.
|
|
160
160
|
|
|
161
|
+
A plugin may also read models from the project it analyses: `project_models(self, root)`
|
|
162
|
+
returns them, from a file at `root` for instance. It is called once per directory check,
|
|
163
|
+
in every process analysing it, before any module. Its models join the plugin's own and
|
|
164
|
+
are part of what cached results depend on. A single-file check reads no project. A
|
|
165
|
+
model declared wrongly raises `ModelError`, which stops the check with its message
|
|
166
|
+
(exit status 2). This is how a project declares validators of its own:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
class DeclaredValidators(ModelPlugin):
|
|
170
|
+
name: ClassVar[str] = "declared-validators"
|
|
171
|
+
|
|
172
|
+
def project_models(self, root: Path) -> Sequence[Model]:
|
|
173
|
+
path = root / "validators.txt"
|
|
174
|
+
lines = path.read_text(encoding="utf-8").splitlines() if path.is_file() else []
|
|
175
|
+
return tuple(Validator(SymbolId(line)) for line in lines if line)
|
|
176
|
+
```
|
|
177
|
+
|
|
161
178
|
A model plugin may also carry `advisories`, a tuple of `Advisory` values
|
|
162
179
|
(`coretrace_python.dependency`) with the package, the vulnerable range and the affected
|
|
163
180
|
symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
|
|
@@ -101,7 +101,11 @@ confidence (`high`, `medium`, `low`).
|
|
|
101
101
|
### Taint flows
|
|
102
102
|
|
|
103
103
|
Reported when attacker-controlled data reaches a sensitive call without passing through
|
|
104
|
-
a sanitizer for that kind of sink.
|
|
104
|
+
a sanitizer for that kind of sink. The sanitizer may also sit inside a project function
|
|
105
|
+
the data goes through, like a helper that escapes before it answers, or a view that
|
|
106
|
+
redirects to `reverse(...)`. It then protects every caller, on every path through the
|
|
107
|
+
function where it is called. What a function stores into an object or a `nonlocal`
|
|
108
|
+
variable is not treated that way yet.
|
|
105
109
|
|
|
106
110
|
| Rule | Reached sink |
|
|
107
111
|
|---|---|
|
|
@@ -235,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
235
239
|
```json
|
|
236
240
|
{
|
|
237
241
|
"schema_version": 1,
|
|
238
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
242
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.9.0"},
|
|
239
243
|
"root": "/home/me/project",
|
|
240
244
|
"findings": [
|
|
241
245
|
{
|
|
@@ -332,6 +336,19 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
|
|
|
332
336
|
`request.form.get('name')` all read it. A function reading it several times is reported
|
|
333
337
|
once, where it first reads it.
|
|
334
338
|
|
|
339
|
+
Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
|
|
340
|
+
serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
|
|
341
|
+
point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
|
|
342
|
+
passed positionally, by `position`, counted as for argument conditions; `"variadic": true`
|
|
343
|
+
extends a position to every later one, as `safe_join(directory, *pathnames)` takes
|
|
344
|
+
(`{"position": 1, "variadic": true}`). A call is then exploitable only when attacker
|
|
345
|
+
input reaches one of them, `"attacker_arguments": [{"argument": "url", "position": 0}]`
|
|
346
|
+
for `requests.get`; attacker input in another argument leaves it reachable. An argument
|
|
347
|
+
unpacked with `*args` or `**kwargs` may fill any of them: like an argument condition it
|
|
348
|
+
leaves undecided, it does not rule the call out, so it counts, and a wrapper forwarding
|
|
349
|
+
`*args, **kwargs` to the entry point still reaches it. An entry point without
|
|
350
|
+
`attacker_arguments` is exploitable through any argument.
|
|
351
|
+
|
|
335
352
|
An `argument` condition is decided at each call. It names the argument by keyword and,
|
|
336
353
|
when it may be passed positionally, by `position` (counted from 0, the receiver of a
|
|
337
354
|
method excluded); `values` are what makes the call affected, written as the analyzer
|
{coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cache.py
RENAMED
|
@@ -25,6 +25,7 @@ from coretrace_python.findings import Confidence, Finding, Severity
|
|
|
25
25
|
from coretrace_python.interprocedural import (
|
|
26
26
|
Arguments,
|
|
27
27
|
CallSite,
|
|
28
|
+
Cleared,
|
|
28
29
|
ExternalCall,
|
|
29
30
|
ExternalSymbol,
|
|
30
31
|
FunctionSummary,
|
|
@@ -41,7 +42,7 @@ from coretrace_python.interprocedural import (
|
|
|
41
42
|
from coretrace_python.semantic.symbols import SymbolId
|
|
42
43
|
from coretrace_python.source import SourceId, SourceSpan
|
|
43
44
|
|
|
44
|
-
CACHE_FORMAT =
|
|
45
|
+
CACHE_FORMAT = 10
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@dataclass(frozen=True)
|
|
@@ -237,6 +238,8 @@ def _encode_call(call: ExternalCall) -> dict[str, Any]:
|
|
|
237
238
|
"location": _encode_span(call.location),
|
|
238
239
|
"call_site": None if call.call_site is None else _encode_span(call.call_site),
|
|
239
240
|
"given": _encode_arguments(call.arguments),
|
|
241
|
+
"argument_cleared": [_encode_cleared(c) for c in call.argument_cleared],
|
|
242
|
+
"keyword_cleared": [_encode_cleared(c) for c in call.keyword_cleared],
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
|
|
@@ -249,9 +252,19 @@ def _decode_call(data: Mapping[str, Any]) -> ExternalCall:
|
|
|
249
252
|
_decode_span(data["location"]),
|
|
250
253
|
None if site is None else _decode_span(site),
|
|
251
254
|
_decode_arguments(data["given"]),
|
|
255
|
+
tuple(_decode_cleared(c) for c in data["argument_cleared"]),
|
|
256
|
+
tuple(_decode_cleared(c) for c in data["keyword_cleared"]),
|
|
252
257
|
)
|
|
253
258
|
|
|
254
259
|
|
|
260
|
+
def _encode_cleared(cleared: Cleared) -> list[list[int]]:
|
|
261
|
+
return [[parameter, bits] for parameter, bits in cleared]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _decode_cleared(data: Any) -> Cleared:
|
|
265
|
+
return tuple((_integer(parameter), _integer(bits)) for parameter, bits in data)
|
|
266
|
+
|
|
267
|
+
|
|
255
268
|
def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
|
|
256
269
|
return {
|
|
257
270
|
"name": summary.name,
|
|
@@ -275,6 +288,7 @@ def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
|
|
|
275
288
|
for w in summary.nonlocal_writes
|
|
276
289
|
],
|
|
277
290
|
"static": summary.static,
|
|
291
|
+
"return_cleared": _encode_cleared(summary.return_cleared),
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
|
|
@@ -303,6 +317,7 @@ def _decode_summary(data: Mapping[str, Any]) -> FunctionSummary:
|
|
|
303
317
|
for w in data["nonlocal_writes"]
|
|
304
318
|
),
|
|
305
319
|
bool(data["static"]),
|
|
320
|
+
_decode_cleared(data["return_cleared"]),
|
|
306
321
|
)
|
|
307
322
|
|
|
308
323
|
|
{coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cli.py
RENAMED
|
@@ -27,6 +27,7 @@ from coretrace_python.reporters import FORMATS, render
|
|
|
27
27
|
from coretrace_python.semantic.imports import ImportResolutionError
|
|
28
28
|
from coretrace_python.semantic.scopes import ScopeError
|
|
29
29
|
from coretrace_python.source import SourceManager
|
|
30
|
+
from coretrace_python.taint import ModelError
|
|
30
31
|
|
|
31
32
|
EXIT_CLEAN = 0
|
|
32
33
|
EXIT_FINDINGS = 1
|
|
@@ -45,6 +46,7 @@ _ANALYSIS_ERRORS = (
|
|
|
45
46
|
AnalysisError,
|
|
46
47
|
ManifestError,
|
|
47
48
|
IncompatiblePluginError,
|
|
49
|
+
ModelError,
|
|
48
50
|
)
|
|
49
51
|
|
|
50
52
|
|
|
@@ -12,6 +12,7 @@ from coretrace_python.dependency.graph import (
|
|
|
12
12
|
DEPENDENCY_FILES,
|
|
13
13
|
Advisory,
|
|
14
14
|
AdvisoryEntryPoint,
|
|
15
|
+
AttackerArgument,
|
|
15
16
|
Condition,
|
|
16
17
|
DependencyAnalysis,
|
|
17
18
|
DependencyGraph,
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"Advisory",
|
|
32
33
|
"AdvisoryEntryPoint",
|
|
33
34
|
"AdvisoryFileError",
|
|
35
|
+
"AttackerArgument",
|
|
34
36
|
"Condition",
|
|
35
37
|
"DependencyAnalysis",
|
|
36
38
|
"DependencyGraph",
|
|
@@ -17,7 +17,13 @@ from collections.abc import Iterable, Iterator, Mapping
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from coretrace_python.dependency.graph import
|
|
20
|
+
from coretrace_python.dependency.graph import (
|
|
21
|
+
Advisory,
|
|
22
|
+
AdvisoryEntryPoint,
|
|
23
|
+
AttackerArgument,
|
|
24
|
+
Condition,
|
|
25
|
+
normalize,
|
|
26
|
+
)
|
|
21
27
|
from coretrace_python.findings import Severity
|
|
22
28
|
from coretrace_python.semantic.symbols import SymbolId
|
|
23
29
|
|
|
@@ -155,6 +161,19 @@ def _entry_point_entry(entry_point: AdvisoryEntryPoint) -> dict[str, Any]:
|
|
|
155
161
|
}
|
|
156
162
|
if entry_point.read:
|
|
157
163
|
entry["read"] = True
|
|
164
|
+
if entry_point.attacker_arguments:
|
|
165
|
+
entry["attacker_arguments"] = [_attacker_argument_entry(a) for a in entry_point.attacker_arguments]
|
|
166
|
+
return entry
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _attacker_argument_entry(argument: AttackerArgument) -> dict[str, Any]:
|
|
170
|
+
entry: dict[str, Any] = {}
|
|
171
|
+
if argument.argument is not None:
|
|
172
|
+
entry["argument"] = argument.argument
|
|
173
|
+
if argument.position is not None:
|
|
174
|
+
entry["position"] = argument.position
|
|
175
|
+
if argument.variadic:
|
|
176
|
+
entry["variadic"] = True
|
|
158
177
|
return entry
|
|
159
178
|
|
|
160
179
|
|
|
@@ -206,9 +225,25 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
|
|
|
206
225
|
str(entry["justification"]),
|
|
207
226
|
tuple(_condition(c) for c in entry.get("conditions") or []),
|
|
208
227
|
read,
|
|
228
|
+
tuple(_attacker_argument(a) for a in entry.get("attacker_arguments") or []),
|
|
209
229
|
)
|
|
210
230
|
|
|
211
231
|
|
|
232
|
+
def _attacker_argument(entry: Mapping[str, Any]) -> AttackerArgument:
|
|
233
|
+
argument, position, variadic = entry.get("argument"), entry.get("position"), entry.get("variadic", False)
|
|
234
|
+
if argument is not None and not isinstance(argument, str):
|
|
235
|
+
raise TypeError(f"attacker argument name must be a string, got {argument!r}")
|
|
236
|
+
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
237
|
+
raise TypeError(f"attacker argument position must be a non-negative integer, got {position!r}")
|
|
238
|
+
if not isinstance(variadic, bool):
|
|
239
|
+
raise TypeError(f"attacker argument variadic must be true or false, got {variadic!r}")
|
|
240
|
+
if argument is None and position is None:
|
|
241
|
+
raise ValueError("an attacker argument needs a name, a position or both")
|
|
242
|
+
if variadic and position is None:
|
|
243
|
+
raise ValueError("a variadic attacker argument needs the position it starts at")
|
|
244
|
+
return AttackerArgument(argument, position, variadic)
|
|
245
|
+
|
|
246
|
+
|
|
212
247
|
def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
213
248
|
position = entry.get("position")
|
|
214
249
|
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
@@ -133,7 +133,10 @@ def correlate(
|
|
|
133
133
|
continue
|
|
134
134
|
hotspot = verdict is not None and verdict.status is Status.HOTSPOT
|
|
135
135
|
for advisory in affected.get(flow.sink.symbol, ()):
|
|
136
|
-
|
|
136
|
+
entry = advisory.entry_point(flow.sink.symbol)
|
|
137
|
+
if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
|
|
138
|
+
continue
|
|
139
|
+
check = check_conditions(entry, flow.sink_arguments)
|
|
137
140
|
if check.contradicted is None:
|
|
138
141
|
findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
|
|
139
142
|
return tuple(findings)
|
|
@@ -166,6 +166,31 @@ class Condition:
|
|
|
166
166
|
return self.kind == "argument"
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class AttackerArgument:
|
|
171
|
+
"""An argument of an entry point that must carry the attacker's input: passed by
|
|
172
|
+
keyword ``argument``, or at ``position`` (from 0, a method's receiver excluded) when it
|
|
173
|
+
may be passed positionally; a ``variadic`` one, such as ``*pathnames``, also takes every
|
|
174
|
+
later position."""
|
|
175
|
+
|
|
176
|
+
argument: str | None = None
|
|
177
|
+
position: int | None = None
|
|
178
|
+
variadic: bool = False
|
|
179
|
+
|
|
180
|
+
def receives(self, position: int | None, keyword: str | None) -> bool:
|
|
181
|
+
"""Whether the argument passed at ``position`` or as ``keyword`` is this one. An
|
|
182
|
+
argument unpacked with ``*`` or ``**`` has neither and may fill this one: like an
|
|
183
|
+
argument condition it leaves undecided, it does not rule the call out, so it is."""
|
|
184
|
+
|
|
185
|
+
if keyword is not None:
|
|
186
|
+
return keyword == self.argument
|
|
187
|
+
if position is None:
|
|
188
|
+
return True
|
|
189
|
+
if self.position is None:
|
|
190
|
+
return False
|
|
191
|
+
return position == self.position or (self.variadic and position > self.position)
|
|
192
|
+
|
|
193
|
+
|
|
169
194
|
@dataclass(frozen=True)
|
|
170
195
|
class AdvisoryEntryPoint:
|
|
171
196
|
"""A public API through which a project reaches an affected symbol, justified by the
|
|
@@ -173,12 +198,20 @@ class AdvisoryEntryPoint:
|
|
|
173
198
|
|
|
174
199
|
An entry point is reached by a call, or — when ``read`` is set, for an attribute whose
|
|
175
200
|
getter runs the affected code, such as a lazily parsed request body — by any read of
|
|
176
|
-
it or of an attribute of it.
|
|
201
|
+
it or of an attribute of it. When it names ``attacker_arguments``, attacker input
|
|
202
|
+
exploits it only through one of them; otherwise through any argument."""
|
|
177
203
|
|
|
178
204
|
symbol: SymbolId
|
|
179
205
|
justification: str
|
|
180
206
|
conditions: tuple[Condition, ...] = ()
|
|
181
207
|
read: bool = False
|
|
208
|
+
attacker_arguments: tuple[AttackerArgument, ...] = ()
|
|
209
|
+
|
|
210
|
+
def exploitable_through(self, position: int | None, keyword: str | None) -> bool:
|
|
211
|
+
"""Whether attacker input in the argument at ``position`` or ``keyword`` can
|
|
212
|
+
exploit the vulnerability."""
|
|
213
|
+
|
|
214
|
+
return not self.attacker_arguments or any(a.receives(position, keyword) for a in self.attacker_arguments)
|
|
182
215
|
|
|
183
216
|
|
|
184
217
|
DIRECT = "affected symbol, changed by the fix"
|
{coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -70,6 +70,7 @@ from coretrace_python.interprocedural import (
|
|
|
70
70
|
CallGraph,
|
|
71
71
|
CallGraphAnalysis,
|
|
72
72
|
CallSite,
|
|
73
|
+
ClearingAnalysis,
|
|
73
74
|
FunctionSummary,
|
|
74
75
|
ModuleFunction,
|
|
75
76
|
ModuleGraph,
|
|
@@ -149,6 +150,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
|
|
|
149
150
|
DependencyAnalysis,
|
|
150
151
|
RegisteredRoutes,
|
|
151
152
|
EscapedTemplates,
|
|
153
|
+
ClearingAnalysis,
|
|
152
154
|
)
|
|
153
155
|
|
|
154
156
|
|
|
@@ -188,6 +190,7 @@ class ResultsEvicted(TransformationPass):
|
|
|
188
190
|
DependencyAnalysis,
|
|
189
191
|
RegisteredRoutes,
|
|
190
192
|
EscapedTemplates,
|
|
193
|
+
ClearingAnalysis,
|
|
191
194
|
}
|
|
192
195
|
)
|
|
193
196
|
|
|
@@ -246,7 +249,9 @@ def build_manager(
|
|
|
246
249
|
"""A manager with every engine analysis registered and the engine inputs provided."""
|
|
247
250
|
|
|
248
251
|
manager = _register_all(module)
|
|
249
|
-
|
|
252
|
+
table = (models or SecurityModelRegistry()).freeze()
|
|
253
|
+
manager.provide(SecurityModelAnalysis, table)
|
|
254
|
+
manager.provide(ClearingAnalysis, table.clearing())
|
|
250
255
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
251
256
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
252
257
|
return manager
|
|
@@ -276,10 +281,14 @@ def load_plugins(plugin_roots: Sequence[Path], manager: AnalysisManager) -> Plug
|
|
|
276
281
|
return registry
|
|
277
282
|
|
|
278
283
|
|
|
279
|
-
def plugin_models(plugins: Iterable[Plugin]) -> ModelTable:
|
|
284
|
+
def plugin_models(plugins: Iterable[Plugin], root: Path | None = None) -> ModelTable:
|
|
285
|
+
"""The models of ``plugins``, with those they read from the project at ``root``."""
|
|
286
|
+
|
|
280
287
|
models = SecurityModelRegistry()
|
|
281
288
|
for plugin in plugins:
|
|
282
289
|
models.register(*plugin.models, origin=plugin.name)
|
|
290
|
+
if root is not None:
|
|
291
|
+
models.register(*plugin.project_models(root), origin=plugin.name)
|
|
283
292
|
return models.freeze()
|
|
284
293
|
|
|
285
294
|
|
|
@@ -294,7 +303,9 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
|
|
|
294
303
|
|
|
295
304
|
manager = _register_all(build_hir(source))
|
|
296
305
|
registry = load_plugins(plugin_roots, manager)
|
|
297
|
-
|
|
306
|
+
table = plugin_models(loaded.plugin for loaded in registry)
|
|
307
|
+
manager.provide(SecurityModelAnalysis, table)
|
|
308
|
+
manager.provide(ClearingAnalysis, table.clearing())
|
|
298
309
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
299
310
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
300
311
|
findings, supported = _check_module(manager, tuple(loaded.plugin for loaded in registry))
|
|
@@ -385,7 +396,7 @@ def analyze_project(
|
|
|
385
396
|
(a for plugin in all_plugins for a in plugin.advisories), file_advisories
|
|
386
397
|
)
|
|
387
398
|
affected = affected_symbols(dependencies, advisories)
|
|
388
|
-
models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
|
|
399
|
+
models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
|
|
389
400
|
for manager in managers.values():
|
|
390
401
|
manager.provide(SecurityModelAnalysis, models)
|
|
391
402
|
manager.provide(DependencyAnalysis, dependencies)
|
|
@@ -408,9 +419,11 @@ def analyze_project(
|
|
|
408
419
|
for symbol, registered in _routes_of(analysable[name]).items():
|
|
409
420
|
routes.setdefault(symbol, registered)
|
|
410
421
|
escaped = escaped_templates(root)
|
|
422
|
+
clearing = models.clearing(escaped)
|
|
411
423
|
for manager in analysable.values():
|
|
412
424
|
manager.provide(RegisteredRoutes, routes)
|
|
413
425
|
manager.provide(EscapedTemplates, escaped)
|
|
426
|
+
manager.provide(ClearingAnalysis, clearing)
|
|
414
427
|
|
|
415
428
|
configuration = _configuration_key(registry, plugins, models, advisories, dependencies, routes, escaped)
|
|
416
429
|
keys = module_keys(
|
|
@@ -620,13 +633,15 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
|
620
633
|
(a for plugin in all_plugins for a in plugin.advisories), file_advisories
|
|
621
634
|
)
|
|
622
635
|
affected = affected_symbols(dependencies, advisories)
|
|
623
|
-
models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
|
|
636
|
+
models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
|
|
624
637
|
routes = _decode_routes(batch.routes)
|
|
638
|
+
escaped = frozenset(batch.escaped)
|
|
625
639
|
for manager in managers.values():
|
|
626
640
|
manager.provide(SecurityModelAnalysis, models)
|
|
627
641
|
manager.provide(DependencyAnalysis, dependencies)
|
|
628
642
|
manager.provide(RegisteredRoutes, routes)
|
|
629
|
-
manager.provide(EscapedTemplates,
|
|
643
|
+
manager.provide(EscapedTemplates, escaped)
|
|
644
|
+
manager.provide(ClearingAnalysis, models.clearing(escaped))
|
|
630
645
|
module_plugins = tuple(p for p in all_plugins if not isinstance(p, ProjectPlugin))
|
|
631
646
|
results = _analyse_managers(managers, decode_index(batch.seed), module_plugins, affected)
|
|
632
647
|
return {name: encode(entry) for name, entry in results.items()}
|
|
@@ -29,7 +29,12 @@ from coretrace_python.abstract import ConstantPropagation, RangeAnalysis, RangeF
|
|
|
29
29
|
from coretrace_python.analysis import AnalysisContext, AnyAnalysis, FunctionAnalysis
|
|
30
30
|
from coretrace_python.cfg import CFG, BlockId, CFGAnalysis, DominanceAnalysis, DominatorTree
|
|
31
31
|
from coretrace_python.hir import nodes
|
|
32
|
-
from coretrace_python.interprocedural import
|
|
32
|
+
from coretrace_python.interprocedural import (
|
|
33
|
+
CallGraph,
|
|
34
|
+
CallGraphAnalysis,
|
|
35
|
+
KnownFunction,
|
|
36
|
+
project_symbol,
|
|
37
|
+
)
|
|
33
38
|
from coretrace_python.ir.model import (
|
|
34
39
|
BoolOp,
|
|
35
40
|
Branch,
|
|
@@ -55,6 +60,7 @@ from coretrace_python.taint import (
|
|
|
55
60
|
TaintAnalysis,
|
|
56
61
|
TaintFacts,
|
|
57
62
|
TaintFlow,
|
|
63
|
+
TaintKind,
|
|
58
64
|
)
|
|
59
65
|
|
|
60
66
|
VALIDATORS = frozenset(
|
|
@@ -214,15 +220,16 @@ class _Judge:
|
|
|
214
220
|
|
|
215
221
|
def interpret(
|
|
216
222
|
self, condition: Value, truth: bool | None
|
|
217
|
-
) -> tuple[dict[Value, str], set[Value]]:
|
|
218
|
-
"""Values the condition proves safe (with the reason
|
|
219
|
-
without proving anything. ``truth`` is ``None``
|
|
223
|
+
) -> tuple[dict[Value, tuple[str, TaintKind]], set[Value]]:
|
|
224
|
+
"""Values the condition proves safe (with the reason and the kinds it proves them
|
|
225
|
+
safe for), and values it mentions without proving anything. ``truth`` is ``None``
|
|
226
|
+
when it is not fixed at the sink.
|
|
220
227
|
|
|
221
228
|
A recognised check evaluated the wrong way (``isdigit()`` known false) yields
|
|
222
229
|
nothing: it is neither a proof nor a reassuring guard."""
|
|
223
230
|
|
|
224
231
|
definition = self.defs.get(condition)
|
|
225
|
-
proven: dict[Value, str] = {}
|
|
232
|
+
proven: dict[Value, tuple[str, TaintKind]] = {}
|
|
226
233
|
mentioned: set[Value] = set()
|
|
227
234
|
if isinstance(definition, UnaryOp) and definition.operator == "not":
|
|
228
235
|
return self.interpret(definition.operand, None if truth is None else not truth)
|
|
@@ -236,27 +243,29 @@ class _Judge:
|
|
|
236
243
|
recognised = self.recognise(definition)
|
|
237
244
|
if recognised is None:
|
|
238
245
|
return proven, set(self.closure(condition)) | {condition}
|
|
239
|
-
tested, reason, when_true = recognised
|
|
246
|
+
tested, reason, when_true, kinds = recognised
|
|
240
247
|
if truth is None:
|
|
241
248
|
mentioned = set(self.closure(condition)) | {condition}
|
|
242
249
|
elif truth == when_true:
|
|
243
250
|
# The check proves ``tested``; whatever else it reads is merely mentioned.
|
|
244
|
-
proven[tested] = reason
|
|
251
|
+
proven[tested] = (reason, kinds)
|
|
245
252
|
mentioned = set(self.closure(condition)) | {condition}
|
|
246
253
|
return proven, mentioned
|
|
247
254
|
|
|
248
|
-
def recognise(self, definition: Instruction | None) -> tuple[Value, str, bool] | None:
|
|
249
|
-
"""``(validated value, reason, truth that validates)`` for known
|
|
255
|
+
def recognise(self, definition: Instruction | None) -> tuple[Value, str, bool, TaintKind] | None:
|
|
256
|
+
"""``(validated value, reason, truth that validates, kinds it proves)`` for known
|
|
257
|
+
check shapes. A string check, a constant allowlist or an equality with a constant
|
|
258
|
+
proves every kind; a ``Validator`` only the kinds it declares."""
|
|
250
259
|
|
|
251
260
|
if isinstance(definition, Call) and not definition.arguments:
|
|
252
261
|
callee = self.defs.get(definition.callee)
|
|
253
262
|
if isinstance(callee, GetAttr) and callee.attribute in VALIDATORS:
|
|
254
|
-
return callee.object, f"guarded by {callee.attribute}()", True
|
|
263
|
+
return callee.object, f"guarded by {callee.attribute}()", True, TaintKind.ALL
|
|
255
264
|
if isinstance(definition, Call):
|
|
256
265
|
symbol = self.symbols.get(definition.callee)
|
|
257
266
|
validator = self.models.validator(symbol) if symbol is not None else None
|
|
258
267
|
if validator is not None and validator.argument < len(definition.arguments):
|
|
259
|
-
return definition.arguments[validator.argument], f"validated by {symbol}", True
|
|
268
|
+
return definition.arguments[validator.argument], f"validated by {symbol}", True, validator.kinds
|
|
260
269
|
if isinstance(definition, Compare):
|
|
261
270
|
left, right = definition.left, definition.right
|
|
262
271
|
if definition.operator in ("in", "not_in") and self.is_constant_collection(right):
|
|
@@ -264,11 +273,12 @@ class _Judge:
|
|
|
264
273
|
left,
|
|
265
274
|
"allowlisted by a membership check on constants",
|
|
266
275
|
definition.operator == "in",
|
|
276
|
+
TaintKind.ALL,
|
|
267
277
|
)
|
|
268
278
|
if definition.operator in ("eq", "not_eq"):
|
|
269
279
|
for tested, other in ((left, right), (right, left)):
|
|
270
280
|
if isinstance(self.defs.get(other), Constant):
|
|
271
|
-
return tested, "equals a constant", definition.operator == "eq"
|
|
281
|
+
return tested, "equals a constant", definition.operator == "eq", TaintKind.ALL
|
|
272
282
|
return None
|
|
273
283
|
|
|
274
284
|
def examined(
|
|
@@ -387,8 +397,9 @@ class _Judge:
|
|
|
387
397
|
)
|
|
388
398
|
for guard in self.guards(sink):
|
|
389
399
|
found, mentioned = self.interpret(guard.condition, guard.truth)
|
|
390
|
-
for value, reason in found.items():
|
|
391
|
-
|
|
400
|
+
for value, (reason, kinds) in found.items():
|
|
401
|
+
# A check proving some kinds only leaves the others reaching the sink.
|
|
402
|
+
if value in chain and not flow.kinds & ~kinds:
|
|
392
403
|
proven.setdefault(value, reason)
|
|
393
404
|
examined = self.examined(guard.condition, mentioned, flow.argument, chain)
|
|
394
405
|
for origin in origins:
|
|
@@ -478,6 +489,7 @@ class RefutationAnalysis(FunctionAnalysis[Verdicts]):
|
|
|
478
489
|
constants = ctx.get(ConstantPropagation, function)
|
|
479
490
|
graph = ctx.get(CallGraphAnalysis)
|
|
480
491
|
models = ctx.get(SecurityModelAnalysis)
|
|
492
|
+
name = graph.name_of(function)
|
|
481
493
|
return judge_flows(
|
|
482
494
|
ssa,
|
|
483
495
|
ctx.get(CFGAnalysis, function),
|
|
@@ -486,7 +498,21 @@ class RefutationAnalysis(FunctionAnalysis[Verdicts]):
|
|
|
486
498
|
frozenset(b.id for b in ssa.blocks if constants.reachable(b.id)),
|
|
487
499
|
ctx.get(RangeAnalysis, function),
|
|
488
500
|
models,
|
|
489
|
-
|
|
501
|
+
_with_project_callees(ssa, graph, name, ctx.module.name),
|
|
490
502
|
authorization_of(function, models, ctx.get(ScopeAnalysis), ctx.get(SymbolAnalysis)),
|
|
491
503
|
)
|
|
492
504
|
|
|
505
|
+
|
|
506
|
+
def _with_project_callees(function: FunctionIR, graph: CallGraph, name: str, module: str) -> dict[Value, SymbolId]:
|
|
507
|
+
"""The symbols of ``name``'s values, with each call to a function of its module named
|
|
508
|
+
by its project symbol, so a model declared on it (``Validator``) applies there too."""
|
|
509
|
+
|
|
510
|
+
symbols = dict(graph.symbols(name))
|
|
511
|
+
for block in function.blocks:
|
|
512
|
+
for instruction in block.instructions:
|
|
513
|
+
if isinstance(instruction, Call):
|
|
514
|
+
target = graph.target_at(name, instruction.location)
|
|
515
|
+
if isinstance(target, KnownFunction):
|
|
516
|
+
symbols.setdefault(instruction.callee, project_symbol(module, target.name))
|
|
517
|
+
return symbols
|
|
518
|
+
|
|
@@ -20,6 +20,9 @@ from coretrace_python.interprocedural.modulegraph import (
|
|
|
20
20
|
project_symbol,
|
|
21
21
|
)
|
|
22
22
|
from coretrace_python.interprocedural.summaries import (
|
|
23
|
+
Cleared,
|
|
24
|
+
Clearing,
|
|
25
|
+
ClearingAnalysis,
|
|
23
26
|
ExternalCall,
|
|
24
27
|
FunctionSummary,
|
|
25
28
|
Mutation,
|
|
@@ -28,6 +31,7 @@ from coretrace_python.interprocedural.summaries import (
|
|
|
28
31
|
SummaryAnalysis,
|
|
29
32
|
SummaryIndex,
|
|
30
33
|
SummaryTable,
|
|
34
|
+
cleared_by,
|
|
31
35
|
)
|
|
32
36
|
|
|
33
37
|
__all__ = [
|
|
@@ -35,6 +39,9 @@ __all__ = [
|
|
|
35
39
|
"CallGraph",
|
|
36
40
|
"CallGraphAnalysis",
|
|
37
41
|
"CallSite",
|
|
42
|
+
"Cleared",
|
|
43
|
+
"Clearing",
|
|
44
|
+
"ClearingAnalysis",
|
|
38
45
|
"ExternalCall",
|
|
39
46
|
"ExternalSymbol",
|
|
40
47
|
"FunctionSummary",
|
|
@@ -51,6 +58,7 @@ __all__ = [
|
|
|
51
58
|
"Target",
|
|
52
59
|
"UnknownTarget",
|
|
53
60
|
"build_module_graph",
|
|
61
|
+
"cleared_by",
|
|
54
62
|
"discover_files",
|
|
55
63
|
"discover_sources",
|
|
56
64
|
"project_symbol",
|