coretrace-python-analyzer 0.8.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.8.0 → coretrace_python_analyzer-0.9.0}/CHANGELOG.md +6 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/docs/usage.md +14 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/__init__.py +2 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/advisories.py +36 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/correlation.py +4 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/graph.py +34 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/engine.py +17 -2
- coretrace_python_analyzer-0.9.0/tests/test_attacker_arguments.py +203 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_release.py +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/README.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/docs/plugins.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/engine.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.8.0 (2026-09-25)
|
|
4
10
|
|
|
5
11
|
### 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
|
|
@@ -239,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
239
239
|
```json
|
|
240
240
|
{
|
|
241
241
|
"schema_version": 1,
|
|
242
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
242
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.9.0"},
|
|
243
243
|
"root": "/home/me/project",
|
|
244
244
|
"findings": [
|
|
245
245
|
{
|
|
@@ -336,6 +336,19 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
|
|
|
336
336
|
`request.form.get('name')` all read it. A function reading it several times is reported
|
|
337
337
|
once, where it first reads it.
|
|
338
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
|
+
|
|
339
352
|
An `argument` condition is decided at each call. It names the argument by keyword and,
|
|
340
353
|
when it may be passed positionally, by `position` (counted from 0, the receiver of a
|
|
341
354
|
method excluded); `values` are what makes the call affected, written as the analyzer
|
|
@@ -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"
|
|
@@ -9,7 +9,7 @@ argument reaching a sink whose kinds it still carries is reported as a ``TaintFl
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from collections.abc import Callable, Mapping
|
|
12
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
13
13
|
from dataclasses import dataclass, replace
|
|
14
14
|
from types import MappingProxyType
|
|
15
15
|
from typing import ClassVar
|
|
@@ -119,6 +119,9 @@ class TaintFlow:
|
|
|
119
119
|
sink_location: SourceSpan | None = None
|
|
120
120
|
# What the arguments of the sink call denote, to decide the conditions on them.
|
|
121
121
|
sink_arguments: Arguments | None = None
|
|
122
|
+
# How the tainted value is passed to the sink call: at a position, by a keyword, or
|
|
123
|
+
# (None, None) unpacked with ``*`` or ``**``; ``f(x, key=x)`` passes it twice.
|
|
124
|
+
passed_as: frozenset[tuple[int | None, str | None]] = frozenset()
|
|
122
125
|
|
|
123
126
|
|
|
124
127
|
Key = Value | HeapLocation
|
|
@@ -559,6 +562,7 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
559
562
|
through,
|
|
560
563
|
call.location if sink_location is None else sink_location,
|
|
561
564
|
arguments,
|
|
565
|
+
frozenset({(position, keyword)}),
|
|
562
566
|
)
|
|
563
567
|
)
|
|
564
568
|
|
|
@@ -819,7 +823,18 @@ def propagate_taint(
|
|
|
819
823
|
state, found = problem.evaluate(block, solution.incoming(block.id))
|
|
820
824
|
taints.update(state)
|
|
821
825
|
flows.extend(found)
|
|
822
|
-
return TaintFacts(taints,
|
|
826
|
+
return TaintFacts(taints, _merged(flows))
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _merged(flows: Iterable[TaintFlow]) -> tuple[TaintFlow, ...]:
|
|
830
|
+
"""One flow per source, sink, kinds and tainted value, however many arguments of the
|
|
831
|
+
sink call it is passed as."""
|
|
832
|
+
|
|
833
|
+
passed: dict[TaintFlow, frozenset[tuple[int | None, str | None]]] = {}
|
|
834
|
+
for flow in flows:
|
|
835
|
+
key = replace(flow, passed_as=frozenset())
|
|
836
|
+
passed[key] = passed.get(key, frozenset()) | flow.passed_as
|
|
837
|
+
return tuple(replace(flow, passed_as=ways) for flow, ways in passed.items())
|
|
823
838
|
|
|
824
839
|
|
|
825
840
|
class TaintAnalysis(FunctionAnalysis[TaintFacts]):
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""An advisory entry point may name the arguments the attacker must control.
|
|
2
|
+
|
|
3
|
+
Most vulnerabilities need attacker input in one argument: the path ``send_from_directory``
|
|
4
|
+
serves, not its ``download_name``; the URL ``requests.get`` fetches, not its body. An
|
|
5
|
+
entry point naming its ``attacker_arguments`` — by keyword and, when they may be passed
|
|
6
|
+
positionally, by ``position``; a ``variadic`` one takes every position from its own on —
|
|
7
|
+
is exploitable only when attacker input reaches one of them, and stays reachable
|
|
8
|
+
otherwise. An argument unpacked with ``*`` or ``**`` may fill any of them: like an
|
|
9
|
+
argument condition it leaves undecided, it does not rule the call out, so it counts. An
|
|
10
|
+
entry point naming none counts every argument, as before.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from coretrace_python import engine
|
|
21
|
+
from coretrace_python.dependency import (
|
|
22
|
+
Advisory,
|
|
23
|
+
AdvisoryEntryPoint,
|
|
24
|
+
AdvisoryFileError,
|
|
25
|
+
AttackerArgument,
|
|
26
|
+
Condition,
|
|
27
|
+
dump_advisories,
|
|
28
|
+
load_advisories,
|
|
29
|
+
)
|
|
30
|
+
from coretrace_python.findings import Finding, Severity
|
|
31
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
32
|
+
|
|
33
|
+
PLUGINS = engine.BUNDLED_PLUGINS
|
|
34
|
+
CRAFTED = (Condition("semantic", "the URL is crafted to split its host"),)
|
|
35
|
+
URL = (AttackerArgument("url", 0),)
|
|
36
|
+
|
|
37
|
+
FETCH = Advisory(
|
|
38
|
+
"CVE-2099-0601",
|
|
39
|
+
"vulnlib",
|
|
40
|
+
"<1.1",
|
|
41
|
+
"fetch sends the credentials of one host to another for a crafted URL",
|
|
42
|
+
Severity.HIGH,
|
|
43
|
+
entry_points=(
|
|
44
|
+
AdvisoryEntryPoint(SymbolId("python.vulnlib.fetch"), "commit abc1234", CRAFTED, attacker_arguments=URL),
|
|
45
|
+
AdvisoryEntryPoint(
|
|
46
|
+
SymbolId("python.vulnlib.Session.fetch"),
|
|
47
|
+
"call path: Session.fetch -> fetch (commit abc1234)",
|
|
48
|
+
CRAFTED,
|
|
49
|
+
attacker_arguments=URL,
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
modules=("vulnlib",),
|
|
53
|
+
)
|
|
54
|
+
JOIN = Advisory(
|
|
55
|
+
"CVE-2099-0602",
|
|
56
|
+
"vulnlib",
|
|
57
|
+
"<1.1",
|
|
58
|
+
"join accepts device names among the parts it joins",
|
|
59
|
+
Severity.MEDIUM,
|
|
60
|
+
entry_points=(
|
|
61
|
+
AdvisoryEntryPoint(
|
|
62
|
+
SymbolId("python.vulnlib.join"),
|
|
63
|
+
"commit def5678",
|
|
64
|
+
(Condition("semantic", "the server runs on Windows"),),
|
|
65
|
+
attacker_arguments=(AttackerArgument(position=1, variadic=True),),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
modules=("vulnlib",),
|
|
69
|
+
)
|
|
70
|
+
SLOW = Advisory(
|
|
71
|
+
"CVE-2099-0603",
|
|
72
|
+
"vulnlib",
|
|
73
|
+
"<1.1",
|
|
74
|
+
"fetch is slow on any large argument",
|
|
75
|
+
Severity.LOW,
|
|
76
|
+
entry_points=(
|
|
77
|
+
AdvisoryEntryPoint(
|
|
78
|
+
SymbolId("python.vulnlib.fetch"), "commit 0a1b2c3", (Condition("semantic", "the argument is large"),)
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
modules=("vulnlib",),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def analyse(root: Path, files: dict[str, str]) -> tuple[Finding, ...]:
|
|
86
|
+
for relative, text in {"requirements.txt": "vulnlib==1.0\n", **files}.items():
|
|
87
|
+
path = root / relative
|
|
88
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
path.write_text(text, encoding="utf-8")
|
|
90
|
+
(root / "advisories.json").write_text(dump_advisories((FETCH, JOIN, SLOW)), encoding="utf-8")
|
|
91
|
+
return engine.analyze_project(root, [PLUGINS]).findings
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def of_rule(findings: tuple[Finding, ...], rule_id: str) -> list[tuple[int, str]]:
|
|
95
|
+
return sorted((f.span.start_line, f.metadata["advisory"]) for f in findings if f.rule_id == rule_id)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def exploitable(root: Path, files: dict[str, str]) -> list[tuple[int, str]]:
|
|
99
|
+
return of_rule(analyse(root, files), "exploitable-vulnerability")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def calling(call: str) -> dict[str, str]:
|
|
103
|
+
return {"app.py": f"import vulnlib\n\ndef run():\n data = input()\n return {call}\n"}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize(
|
|
107
|
+
"call, advisories",
|
|
108
|
+
[
|
|
109
|
+
("vulnlib.fetch(data)", ["CVE-2099-0601", "CVE-2099-0603"]),
|
|
110
|
+
("vulnlib.fetch(url=data)", ["CVE-2099-0601", "CVE-2099-0603"]),
|
|
111
|
+
("vulnlib.Session().fetch(data)", ["CVE-2099-0601"]),
|
|
112
|
+
("vulnlib.join('uploads', data)", ["CVE-2099-0602"]),
|
|
113
|
+
("vulnlib.join('uploads', 'reports', data)", ["CVE-2099-0602"]),
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
def test_attacker_input_in_a_named_argument_is_exploitable(tmp_path: Path, call: str, advisories: list[str]) -> None:
|
|
117
|
+
assert exploitable(tmp_path, calling(call)) == [(5, a) for a in advisories]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize(
|
|
121
|
+
"call, advisories, reached",
|
|
122
|
+
[
|
|
123
|
+
("vulnlib.fetch('https://example.com', body=data)", ["CVE-2099-0603"], "CVE-2099-0601"),
|
|
124
|
+
("vulnlib.fetch('https://example.com', data)", ["CVE-2099-0603"], "CVE-2099-0601"),
|
|
125
|
+
("vulnlib.Session().fetch('https://example.com', body=data)", [], "CVE-2099-0601"),
|
|
126
|
+
("vulnlib.join(data, 'report.pdf')", [], "CVE-2099-0602"),
|
|
127
|
+
],
|
|
128
|
+
)
|
|
129
|
+
def test_attacker_input_in_another_argument_leaves_the_call_reachable_only(
|
|
130
|
+
tmp_path: Path, call: str, advisories: list[str], reached: str
|
|
131
|
+
) -> None:
|
|
132
|
+
found = analyse(tmp_path, calling(call))
|
|
133
|
+
|
|
134
|
+
assert of_rule(found, "exploitable-vulnerability") == [(5, a) for a in advisories]
|
|
135
|
+
assert (5, reached) in of_rule(found, "reachable-vulnerability")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize(
|
|
139
|
+
"call, advisories",
|
|
140
|
+
[
|
|
141
|
+
("vulnlib.fetch(*data.split(','))", ["CVE-2099-0601", "CVE-2099-0603"]),
|
|
142
|
+
("vulnlib.fetch(**{'url': data})", ["CVE-2099-0601", "CVE-2099-0603"]),
|
|
143
|
+
("vulnlib.join('uploads', *data.split('/'))", ["CVE-2099-0602"]),
|
|
144
|
+
],
|
|
145
|
+
)
|
|
146
|
+
def test_an_unpacked_argument_may_fill_a_named_one_so_it_counts(tmp_path: Path, call: str, advisories: list[str]) -> None:
|
|
147
|
+
assert exploitable(tmp_path, calling(call)) == [(5, a) for a in advisories]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_a_wrapper_forwarding_its_arguments_unpacked_still_reaches_the_named_one(tmp_path: Path) -> None:
|
|
151
|
+
source = (
|
|
152
|
+
"import vulnlib\n\n"
|
|
153
|
+
"def fetch(*args, **kwargs):\n return vulnlib.fetch(*args, timeout=5, **kwargs)\n\n"
|
|
154
|
+
"def run():\n fetch(input())\n"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
assert exploitable(tmp_path, {"app.py": source}) == [(7, "CVE-2099-0601"), (7, "CVE-2099-0603")]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_the_argument_of_the_entry_point_call_decides_when_the_flow_goes_through_a_function(tmp_path: Path) -> None:
|
|
161
|
+
source = (
|
|
162
|
+
"import vulnlib\n\n"
|
|
163
|
+
"def get(url, body):\n return vulnlib.fetch(url, body=body)\n\n"
|
|
164
|
+
"def run():\n get('https://example.com', input())\n get(input(), None)\n"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assert exploitable(tmp_path, {"app.py": source}) == [
|
|
168
|
+
(7, "CVE-2099-0603"),
|
|
169
|
+
(8, "CVE-2099-0601"),
|
|
170
|
+
(8, "CVE-2099-0603"),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_attacker_arguments_round_trip_through_advisory_files(tmp_path: Path) -> None:
|
|
175
|
+
path = tmp_path / "advisories.json"
|
|
176
|
+
path.write_text(dump_advisories((FETCH, JOIN, SLOW)), encoding="utf-8")
|
|
177
|
+
|
|
178
|
+
assert load_advisories(path) == (FETCH, JOIN, SLOW)
|
|
179
|
+
entries = [e for a in json.loads(path.read_text())["advisories"] for e in a["entry_points"]]
|
|
180
|
+
assert entries[0]["attacker_arguments"] == [{"argument": "url", "position": 0}]
|
|
181
|
+
assert entries[2]["attacker_arguments"] == [{"position": 1, "variadic": True}]
|
|
182
|
+
assert "attacker_arguments" not in entries[3]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@pytest.mark.parametrize(
|
|
186
|
+
"arguments",
|
|
187
|
+
[
|
|
188
|
+
[{}],
|
|
189
|
+
[{"position": -1}],
|
|
190
|
+
[{"position": True}],
|
|
191
|
+
[{"argument": "url", "variadic": True}],
|
|
192
|
+
[{"position": 1, "variadic": "yes"}],
|
|
193
|
+
[{"argument": 7}],
|
|
194
|
+
],
|
|
195
|
+
)
|
|
196
|
+
def test_a_malformed_attacker_argument_is_rejected(tmp_path: Path, arguments: list[object]) -> None:
|
|
197
|
+
document = json.loads(dump_advisories((FETCH,)))
|
|
198
|
+
document["advisories"][0]["entry_points"][0]["attacker_arguments"] = arguments
|
|
199
|
+
path = tmp_path / "advisories.json"
|
|
200
|
+
path.write_text(json.dumps(document), encoding="utf-8")
|
|
201
|
+
|
|
202
|
+
with pytest.raises(AdvisoryFileError):
|
|
203
|
+
load_advisories(path)
|
|
@@ -30,7 +30,7 @@ def require_changelog() -> None:
|
|
|
30
30
|
def test_the_version_is_declared_once_and_mirrored() -> None:
|
|
31
31
|
project = tomllib.loads((REPO / "pyproject.toml").read_text(encoding="utf-8"))["project"]
|
|
32
32
|
|
|
33
|
-
assert project["version"] == coretrace_python.__version__ == "0.
|
|
33
|
+
assert project["version"] == coretrace_python.__version__ == "0.9.0"
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_the_changelog_starts_with_the_current_version() -> None:
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/examples/canonical_symbols.py
RENAMED
|
File without changes
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|