coretrace-python-analyzer 0.2.0__tar.gz → 0.4.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.2.0 → coretrace_python_analyzer-0.4.0}/CHANGELOG.md +34 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/docs/plugins.md +51 -5
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/docs/usage.md +66 -6
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/__init__.py +1 -1
- coretrace_python_analyzer-0.4.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +46 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +2 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +3 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cache.py +11 -8
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/__init__.py +4 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/advisories.py +42 -5
- coretrace_python_analyzer-0.4.0/src/coretrace_python/dependency/correlation.py +113 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/graph.py +74 -5
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/engine.py +18 -8
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/callgraph.py +9 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/modulegraph.py +4 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/lowering.py +57 -3
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/__init__.py +4 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/api.py +47 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/loader.py +18 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/scopes.py +3 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/engine.py +25 -5
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/models.py +33 -12
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Chatbot.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/DjangoGoat.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Flask-Image-Editing-Website.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/InsecureGram.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/MiniBookApiServer.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/NPL-IA.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Spam-Detection-Project.json +2 -2
- coretrace_python_analyzer-0.4.0/tests/regression/expected/Vulnerable_fastapi.json +136 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/dvpwa.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/full-stack-fastapi-template.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/healthchecks.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/httpie.json +8 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/luigi.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/my_gdb.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/network-topologer.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/pygoat.json +8 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/vulnerable-flask.json +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/yt-fts.json +2 -2
- coretrace_python_analyzer-0.4.0/tests/test_advisory_evidence.py +218 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_advisory_feeds.py +3 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_baseline.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_cache.py +20 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_class_keywords_with_targets.py +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_closures.py +5 -5
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_dependencies.py +12 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_discovery.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_interprocedural_taint.py +13 -2
- coretrace_python_analyzer-0.4.0/tests/test_module_level.py +146 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_plugins.py +71 -3
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_project.py +90 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_pyir_imports.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_release.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_remaining_limits.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_control_flow.py +2 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_coverage.py +4 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_taint.py +69 -3
- coretrace_python_analyzer-0.2.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -56
- coretrace_python_analyzer-0.2.0/src/coretrace_python/dependency/correlation.py +0 -87
- coretrace_python_analyzer-0.2.0/tests/regression/expected/Vulnerable_fastapi.json +0 -46
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/README.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/tests/test_tornado_bottle_models.py +0 -0
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-09-24)
|
|
4
|
+
|
|
5
|
+
### Analysis
|
|
6
|
+
|
|
7
|
+
- The module body and the bodies of top-level classes are analysed as the functions `<module>` and `Cls.<body>`: their calls are call sites, their values carry taint, and an advisory entry point called at import time or in a class body is reachable. Coverage counts them; the cache format is bumped so cached results gain their findings (#113).
|
|
8
|
+
|
|
9
|
+
### Dependencies
|
|
10
|
+
|
|
11
|
+
- Advisories distinguish the `affected_symbols` a fix changed from the public `entry_points` through which they are reached, each justified and carrying `conditions` (checkable argument values, or `semantic` ones kept as pending review); `modules` names what the package installs. Every dependency finding records its evidence `level`: `declared`, `imported`, `reachable` or `exploitable` (#109).
|
|
12
|
+
- A call to an API affected by several advisories of the pinned release reports every one of them, not the first listed (#109).
|
|
13
|
+
- One advisory per identifier and package: a later contributor replaces an earlier one, so a curated feed refines the bundled sample, and the sample names the modules its packages install (#109).
|
|
14
|
+
- A version specifier may be a union of ranges separated by `||`, as an advisory over several release series needs (`>=4.2,<4.2.28 || >=5.2,<5.2.11`); the OSV import produces one such advisory per package instead of one per range (#112).
|
|
15
|
+
|
|
16
|
+
### Plugins
|
|
17
|
+
|
|
18
|
+
- Every plugin module is registered in `sys.modules` before it runs, so a single-file plugin may define a dataclass under `from __future__ import annotations` (#105).
|
|
19
|
+
- `ProjectPlugin.refine(ctx, findings)` lets a project plugin reclassify the findings of a run: severity, confidence and added metadata only, with `refined_by` and the original values recorded; anything else raises `RefinementError` (#106).
|
|
20
|
+
|
|
21
|
+
## 0.3.0 (2026-09-22)
|
|
22
|
+
|
|
23
|
+
### Plugins
|
|
24
|
+
|
|
25
|
+
- Two plugins may declare the same symbol: an identical model is registered once and sinks merge their kinds and positions; a real conflict names both plugins instead of failing the run on the first duplicate (#92).
|
|
26
|
+
- A plugin's entrypoint may be a package, `<module>/__init__.py`, so a plugin can split its tables across files and import them relatively (#93).
|
|
27
|
+
- The cache key covers every file of a plugin directory, not only `.py` and `.toml`, so rules or advisories shipped as data invalidate cached results when they change (#94).
|
|
28
|
+
|
|
29
|
+
### Analysis
|
|
30
|
+
|
|
31
|
+
- New taint kinds: `NOSQL` in `TaintKind.ALL`; `LOG` and `PII` outside it, carried only by the values a model marks, so a NoSQL-injection, log-injection or personal-data detector no longer has to share a kind with another rule (#96).
|
|
32
|
+
|
|
33
|
+
### Precision
|
|
34
|
+
|
|
35
|
+
- A `Sanitizer` declared on a function of the analysed project takes precedence over the summary derived from its body, in the same file and across files, so a code base can declare its own validation layer (#95).
|
|
36
|
+
|
|
3
37
|
## 0.2.0 (2026-09-06)
|
|
4
38
|
|
|
5
39
|
### Adoption in an existing repository
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -38,7 +38,12 @@ class = "SensitiveLoggingPlugin"
|
|
|
38
38
|
| `plugin_api` | The range of plugin API versions the plugin supports. The current API is version 1. |
|
|
39
39
|
| `requires` | Capabilities the plugin needs; a mismatch with the class's `requires` is a manifest error. |
|
|
40
40
|
| `provides` | Capabilities the plugin offers, for documentation and conflict detection. |
|
|
41
|
-
| `entrypoint` | The module
|
|
41
|
+
| `entrypoint` | The module (without `.py`, next to the manifest) and the class in it. |
|
|
42
|
+
|
|
43
|
+
The entrypoint module may be a package, `<module>/__init__.py`, when a plugin grows past
|
|
44
|
+
one file: its submodules are reached through relative imports (`from .tables import
|
|
45
|
+
celery`). Two plugins may use the same module name; each is imported under a name derived
|
|
46
|
+
from its path.
|
|
42
47
|
|
|
43
48
|
## Symbols
|
|
44
49
|
|
|
@@ -101,10 +106,14 @@ class CommandInjectionPlugin(TaintDetector):
|
|
|
101
106
|
```
|
|
102
107
|
|
|
103
108
|
`confidence` defaults to high; a hotspot, a flow behind a guard that mentions the value
|
|
104
|
-
or an authorization decorator, is reported at medium. The taint kinds
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
or an authorization decorator, is reported at medium. The taint kinds every source
|
|
110
|
+
carries by default (`TaintKind.ALL`) are `SQL`, `NOSQL`, `COMMAND`, `HTML`, `PATH`,
|
|
111
|
+
`SSRF`, `CODE`, `DESERIALIZATION`, `REDIRECT` and `ADVISORY` (APIs affected by
|
|
112
|
+
advisories). Three kinds are outside `ALL`, so only a value a model marks with them
|
|
113
|
+
reaches their sinks: `CREDENTIAL`, which credential-named parameters carry so that
|
|
114
|
+
ordinary input reaching a database write is not a plaintext credential; `LOG`, for
|
|
115
|
+
values that must not be written to a log as they are; and `PII`, for personal data
|
|
116
|
+
that must not leave the application through a log, a third party or plain storage.
|
|
108
117
|
|
|
109
118
|
### Security models: `ModelPlugin`
|
|
110
119
|
|
|
@@ -142,12 +151,49 @@ class BottleModels(ModelPlugin):
|
|
|
142
151
|
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe, such as `re.fullmatch`; a flow guarded by it is refuted. |
|
|
143
152
|
| `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
|
|
144
153
|
|
|
154
|
+
Two plugins may describe the same symbol. An identical model is registered once; two
|
|
155
|
+
`Sink` models merge their kinds and positions, so a plugin can add `SQL` to a sink
|
|
156
|
+
another plugin declared for `COMMAND`; any other difference is a conflict that stops
|
|
157
|
+
the analysis with a `ModelError` naming both plugins.
|
|
158
|
+
|
|
145
159
|
A model plugin may also carry `advisories`, a tuple of `Advisory` values
|
|
146
160
|
(`coretrace_python.dependency`) with the package, the vulnerable range and the affected
|
|
147
161
|
symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
|
|
148
162
|
reported, calls to the affected symbols become reachable vulnerabilities and tainted
|
|
149
163
|
calls become exploitable ones.
|
|
150
164
|
|
|
165
|
+
### Reclassifying findings: `ProjectPlugin.refine`
|
|
166
|
+
|
|
167
|
+
A project plugin may also implement `refine(ctx, findings)`, called once with every
|
|
168
|
+
finding of the run after all plugins have reported. It returns the same findings, in
|
|
169
|
+
the same order, and may only change their `severity` and `confidence` or add metadata:
|
|
170
|
+
the rule, the location, the message and the evidence a finding already carries are not
|
|
171
|
+
the refiner's to touch, and the engine rejects a refinement that changes or drops any
|
|
172
|
+
of them with a `RefinementError`. A reclassified finding records `refined_by` and the
|
|
173
|
+
`original_severity` or `original_confidence` it had, so a report always shows what the
|
|
174
|
+
detector said and who changed it.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from dataclasses import replace
|
|
178
|
+
|
|
179
|
+
from coretrace_python.findings import Confidence
|
|
180
|
+
from coretrace_python.plugins import ProjectContext, ProjectPlugin
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class InternalOnly(ProjectPlugin):
|
|
184
|
+
name = "internal-only"
|
|
185
|
+
|
|
186
|
+
def analyze_project(self, ctx: ProjectContext):
|
|
187
|
+
return ()
|
|
188
|
+
|
|
189
|
+
def refine(self, ctx: ProjectContext, findings):
|
|
190
|
+
return tuple(
|
|
191
|
+
replace(f, confidence=Confidence.LOW, metadata={**f.metadata, "exposure": "internal"})
|
|
192
|
+
if f.function in INTERNAL else f
|
|
193
|
+
for f in findings
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
151
197
|
### Secret scanners: `SecretDetector`
|
|
152
198
|
|
|
153
199
|
Judges every string literal of the module, or every value of a configuration file,
|
|
@@ -71,7 +71,10 @@ its package (`app/views.py` is `app.views`). Hidden directories, `node_modules`,
|
|
|
71
71
|
dependency graph. Configuration files under the root (`.env`, YAML, TOML, JSON, INI,
|
|
72
72
|
properties) are scanned for secrets.
|
|
73
73
|
|
|
74
|
-
Functions and methods are analysed
|
|
74
|
+
Functions and methods are analysed, and so are the module body and the bodies of
|
|
75
|
+
top-level classes, as the functions `<module>` and `Cls.<body>`: `application =
|
|
76
|
+
get_asgi_application()` in `asgi.py` or `email = forms.EmailField()` in a form class are
|
|
77
|
+
calls the analyzer sees, and a module-level `os.system(input())` is a finding in `<module>`.
|
|
75
78
|
A function using syntax outside the supported subset is reported as an
|
|
76
79
|
`unsupported-syntax` note and the other functions are still analysed; a file Python
|
|
77
80
|
itself cannot parse is reported as a `syntax-error`. The coverage line and the JSON
|
|
@@ -198,7 +201,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
198
201
|
```json
|
|
199
202
|
{
|
|
200
203
|
"schema_version": 1,
|
|
201
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
204
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.4.0"},
|
|
202
205
|
"root": "/home/me/project",
|
|
203
206
|
"findings": [
|
|
204
207
|
{
|
|
@@ -241,7 +244,8 @@ coretrace-python-analyzer --check src/ --advisories advisories.json
|
|
|
241
244
|
|
|
242
245
|
A directory check reads `advisories.json` at the project root and every file passed
|
|
243
246
|
with `--advisories`; a local entry wins over a plugin's for the same advisory. The file
|
|
244
|
-
lists advisories with the package, the vulnerable version range
|
|
247
|
+
lists advisories with the package, the vulnerable version range (a specifier, or several
|
|
248
|
+
separated by `||` when several release series are affected) and, optionally, the
|
|
245
249
|
affected APIs that feed reachability and correlation:
|
|
246
250
|
|
|
247
251
|
```json
|
|
@@ -254,13 +258,45 @@ affected APIs that feed reachability and correlation:
|
|
|
254
258
|
"vulnerable": "<5.4",
|
|
255
259
|
"summary": "yaml.load can execute arbitrary code from untrusted documents",
|
|
256
260
|
"severity": "critical",
|
|
257
|
-
"affected_symbols": ["python.yaml.
|
|
261
|
+
"affected_symbols": ["python.yaml.constructor.FullConstructor.construct_python_object_apply"],
|
|
262
|
+
"entry_points": [
|
|
263
|
+
{
|
|
264
|
+
"symbol": "python.yaml.full_load",
|
|
265
|
+
"justification": "FullLoader uses FullConstructor (commit 5080ba5)",
|
|
266
|
+
"conditions": [
|
|
267
|
+
{"kind": "semantic", "text": "the document carries a python/object/apply tag"}
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"symbol": "python.yaml.load",
|
|
272
|
+
"justification": "same path when Loader is FullLoader",
|
|
273
|
+
"conditions": [
|
|
274
|
+
{"kind": "argument", "text": "Loader is FullLoader", "argument": "Loader", "values": ["python.yaml.FullLoader"]},
|
|
275
|
+
{"kind": "semantic", "text": "the document carries a python/object/apply tag"}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
"modules": ["yaml"],
|
|
258
280
|
"aliases": ["GHSA-8q59-q68h-6hv4"]
|
|
259
281
|
}
|
|
260
282
|
]
|
|
261
283
|
}
|
|
262
284
|
```
|
|
263
285
|
|
|
286
|
+
`affected_symbols` are the functions the fix changed; `entry_points` the public APIs
|
|
287
|
+
through which a project reaches them, each with the justification that ties it to the
|
|
288
|
+
fix — the commit, or the call path — and the `conditions` under which it is affected: an
|
|
289
|
+
argument value the engine can check, or a `semantic` condition it cannot yet, kept on the
|
|
290
|
+
finding as pending review rather than dropped. A call to either kind of symbol is
|
|
291
|
+
reachable. `modules` names the top-level modules the package installs, so the analyzer
|
|
292
|
+
can tell an imported package from a merely required one; it defaults to the package name.
|
|
293
|
+
|
|
294
|
+
Every dependency finding records the highest level of evidence established in its
|
|
295
|
+
`level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
|
|
296
|
+
(a module of the package is imported somewhere), `reachable` (an entry point or affected
|
|
297
|
+
symbol is called) or `exploitable` (attacker input reaches it), with `entry_point`,
|
|
298
|
+
`justification`, `conditions` and `conditions_pending_review` on the last two.
|
|
299
|
+
|
|
264
300
|
A `coretrace-policy.toml` at the root, or the file passed with `--policy`, denies
|
|
265
301
|
packages, requires pins and lists accepted advisories whose findings are dropped:
|
|
266
302
|
|
|
@@ -283,8 +319,9 @@ coretrace-python-analyzer --check src/ --sbom sbom.json --policy security/policy
|
|
|
283
319
|
## Large projects
|
|
284
320
|
|
|
285
321
|
`--cache DIR` keeps the results of a directory check on disk, one entry per module,
|
|
286
|
-
keyed by the module's source, the tool
|
|
287
|
-
|
|
322
|
+
keyed by the module's source, the tool version, every file of every plugin directory
|
|
323
|
+
(code, manifest and data alike), the security models, the advisories, the dependency
|
|
324
|
+
graph and the modules it imports. On the next run an unchanged
|
|
288
325
|
module is served from the cache, so editing one file re-analyses that file and its
|
|
289
326
|
importers only. Entries are plain data; an unreadable entry is recomputed.
|
|
290
327
|
|
|
@@ -335,6 +372,29 @@ coretrace-python-analyzer --check src/ --plugins ./coretrace-plugins
|
|
|
335
372
|
coretrace-python-analyzer --check src/ --no-bundled-plugins --plugins ./coretrace-plugins
|
|
336
373
|
```
|
|
337
374
|
|
|
375
|
+
### Your own validation layer
|
|
376
|
+
|
|
377
|
+
A code base that already validates its input has functions the analyzer cannot know are
|
|
378
|
+
safe: a `clean()` that returns its argument looks, from its body, like a function that
|
|
379
|
+
passes taint through. Declare them in a model plugin by their project symbol,
|
|
380
|
+
`python.<module>.<function>`, and the analyzer takes your word over what it derived:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
from coretrace_python.plugins import ModelPlugin
|
|
384
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
385
|
+
from coretrace_python.taint import Sanitizer, TaintKind, Validator
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class HouseModels(ModelPlugin):
|
|
389
|
+
name = "house-models"
|
|
390
|
+
models = (
|
|
391
|
+
# the result of app.validation.clean is safe for commands, whatever the body does
|
|
392
|
+
Sanitizer(SymbolId("python.app.validation.clean"), TaintKind.COMMAND),
|
|
393
|
+
# a flow behind ``if app.validation.is_slug(value):`` is refuted
|
|
394
|
+
Validator(SymbolId("python.app.validation.is_slug")),
|
|
395
|
+
)
|
|
396
|
+
```
|
|
397
|
+
|
|
338
398
|
## Continuous integration
|
|
339
399
|
|
|
340
400
|
The exit status gates the job and the SARIF report feeds code scanning. On GitHub
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Calls, anywhere in the project, to an API affected by a vulnerable requirement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import ClassVar
|
|
7
|
+
|
|
8
|
+
from coretrace_python.analysis import AnyAnalysis
|
|
9
|
+
from coretrace_python.dependency import DependencyAnalysis
|
|
10
|
+
from coretrace_python.dependency.correlation import affected_symbols, evidence
|
|
11
|
+
from coretrace_python.findings import Confidence, Finding
|
|
12
|
+
from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
|
|
13
|
+
from coretrace_python.plugins import ProjectContext, ProjectPlugin
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ReachableVulnerabilityPlugin(ProjectPlugin):
|
|
17
|
+
name: ClassVar[str] = "reachable-vulnerability"
|
|
18
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
|
|
19
|
+
|
|
20
|
+
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
21
|
+
affected = affected_symbols(ctx.dependencies, ctx.advisories)
|
|
22
|
+
if not affected:
|
|
23
|
+
return ()
|
|
24
|
+
findings: list[Finding] = []
|
|
25
|
+
for module in ctx.modules:
|
|
26
|
+
graph = ctx.call_graph(module)
|
|
27
|
+
for function in graph.functions:
|
|
28
|
+
for site in graph.sites(function):
|
|
29
|
+
if not isinstance(site.target, ExternalSymbol):
|
|
30
|
+
continue
|
|
31
|
+
for advisory in affected.get(site.target.symbol, ()):
|
|
32
|
+
findings.append(
|
|
33
|
+
Finding(
|
|
34
|
+
rule_id="reachable-vulnerability",
|
|
35
|
+
message=(
|
|
36
|
+
f"{advisory.id}: {site.target.symbol} is affected in the required "
|
|
37
|
+
f"{advisory.package} {advisory.vulnerable}: {advisory.summary}"
|
|
38
|
+
),
|
|
39
|
+
severity=advisory.severity,
|
|
40
|
+
confidence=Confidence.HIGH,
|
|
41
|
+
span=site.location,
|
|
42
|
+
function=function,
|
|
43
|
+
metadata=evidence(advisory, site.target.symbol, "reachable"),
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
return findings
|
|
@@ -28,6 +28,7 @@ class SampleAdvisories(ModelPlugin):
|
|
|
28
28
|
"yaml.load and full_load can execute arbitrary code from untrusted documents",
|
|
29
29
|
Severity.CRITICAL,
|
|
30
30
|
(_sym("yaml.load"), _sym("yaml.full_load"), _sym("yaml.unsafe_load")),
|
|
31
|
+
modules=("yaml",),
|
|
31
32
|
),
|
|
32
33
|
Advisory(
|
|
33
34
|
"CVE-2018-18074",
|
|
@@ -105,5 +106,6 @@ class SampleAdvisories(ModelPlugin):
|
|
|
105
106
|
Severity.HIGH,
|
|
106
107
|
(_sym("PIL.Image.open"),),
|
|
107
108
|
("GHSA-cfh3-3jmp-rvhc",),
|
|
109
|
+
modules=("PIL",),
|
|
108
110
|
),
|
|
109
111
|
)
|
|
@@ -17,11 +17,13 @@ class VulnerableDependencyPlugin(ProjectPlugin):
|
|
|
17
17
|
|
|
18
18
|
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
19
19
|
findings: list[Finding] = []
|
|
20
|
+
imported = [s for module in ctx.modules for s in ctx.imports(module).all_symbols()]
|
|
20
21
|
for requirement in ctx.dependencies.requirements:
|
|
21
22
|
for advisory in ctx.advisories:
|
|
22
23
|
if not advisory.affects(requirement):
|
|
23
24
|
continue
|
|
24
25
|
pinned = requirement.pinned is not None
|
|
26
|
+
level = "imported" if advisory.imported_by(imported) else "declared"
|
|
25
27
|
findings.append(
|
|
26
28
|
Finding(
|
|
27
29
|
rule_id="vulnerable-dependency",
|
|
@@ -37,6 +39,7 @@ class VulnerableDependencyPlugin(ProjectPlugin):
|
|
|
37
39
|
"advisory": advisory.id,
|
|
38
40
|
"package": advisory.package,
|
|
39
41
|
"specifier": requirement.specifier,
|
|
42
|
+
"level": level,
|
|
40
43
|
},
|
|
41
44
|
)
|
|
42
45
|
)
|
{coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cache.py
RENAMED
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import json
|
|
16
16
|
import os
|
|
17
|
-
from collections.abc import
|
|
17
|
+
from collections.abc import Mapping
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from types import MappingProxyType
|
|
@@ -37,7 +37,7 @@ from coretrace_python.interprocedural import (
|
|
|
37
37
|
from coretrace_python.semantic.symbols import SymbolId
|
|
38
38
|
from coretrace_python.source import SourceId, SourceSpan
|
|
39
39
|
|
|
40
|
-
CACHE_FORMAT =
|
|
40
|
+
CACHE_FORMAT = 5
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@dataclass(frozen=True)
|
|
@@ -64,15 +64,18 @@ def fingerprint(*parts: str) -> str:
|
|
|
64
64
|
return digest.hexdigest()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def directory_fingerprint(directory: Path
|
|
68
|
-
"""A digest of
|
|
67
|
+
def directory_fingerprint(directory: Path) -> str:
|
|
68
|
+
"""A digest of every file under ``directory``, so an edited plugin misses: its code,
|
|
69
|
+
its manifest and the rule or advisory data it ships in any format. Bytecode and
|
|
70
|
+
hidden files are not part of what the plugin does."""
|
|
69
71
|
|
|
70
|
-
wanted = tuple(suffixes)
|
|
71
72
|
parts: list[str] = []
|
|
72
73
|
for path in sorted(directory.rglob("*")):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
relative = path.relative_to(directory)
|
|
75
|
+
if not path.is_file() or any(p == "__pycache__" or p.startswith(".") for p in relative.parts):
|
|
76
|
+
continue
|
|
77
|
+
parts.append(str(relative))
|
|
78
|
+
parts.append(path.read_bytes().hex())
|
|
76
79
|
return fingerprint(*parts)
|
|
77
80
|
|
|
78
81
|
|
|
@@ -11,6 +11,8 @@ from coretrace_python.dependency.advisories import (
|
|
|
11
11
|
from coretrace_python.dependency.graph import (
|
|
12
12
|
DEPENDENCY_FILES,
|
|
13
13
|
Advisory,
|
|
14
|
+
AdvisoryEntryPoint,
|
|
15
|
+
Condition,
|
|
14
16
|
DependencyAnalysis,
|
|
15
17
|
DependencyGraph,
|
|
16
18
|
Requirement,
|
|
@@ -26,7 +28,9 @@ __all__ = [
|
|
|
26
28
|
"DEPENDENCY_FILES",
|
|
27
29
|
"POLICY_FILE",
|
|
28
30
|
"Advisory",
|
|
31
|
+
"AdvisoryEntryPoint",
|
|
29
32
|
"AdvisoryFileError",
|
|
33
|
+
"Condition",
|
|
30
34
|
"DependencyAnalysis",
|
|
31
35
|
"DependencyGraph",
|
|
32
36
|
"Policy",
|
|
@@ -5,8 +5,8 @@ OSV dump into ``Advisory`` values, keeping the PyPI ecosystem and turning each r
|
|
|
5
5
|
events into a version specifier; ``dump_advisories`` writes them as a small JSON file
|
|
6
6
|
that a project keeps at its root as ``advisories.json`` or passes with ``--advisories``.
|
|
7
7
|
OSV records name no affected APIs, so imported advisories feed the requirement checks
|
|
8
|
-
and the SBOM; a file completed by hand with ``affected_symbols``
|
|
9
|
-
reachability and correlation checks.
|
|
8
|
+
and the SBOM; a file completed by hand with ``affected_symbols``, ``entry_points`` and
|
|
9
|
+
their ``conditions`` also feeds the reachability and correlation checks.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -17,7 +17,7 @@ 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 Advisory, normalize
|
|
20
|
+
from coretrace_python.dependency.graph import Advisory, AdvisoryEntryPoint, Condition, normalize
|
|
21
21
|
from coretrace_python.findings import Severity
|
|
22
22
|
from coretrace_python.semantic.symbols import SymbolId
|
|
23
23
|
|
|
@@ -84,8 +84,9 @@ def import_osv(records: Iterable[Mapping[str, Any]]) -> tuple[Advisory, ...]:
|
|
|
84
84
|
if str(package.get("ecosystem", "")).lower() != "pypi" or not package.get("name"):
|
|
85
85
|
continue
|
|
86
86
|
name = normalize(str(package["name"]))
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
specifiers = _specifiers(affected.get("ranges") or [])
|
|
88
|
+
if specifiers:
|
|
89
|
+
advisories.append(Advisory(identifier, name, " || ".join(specifiers), summary, severity, (), aliases))
|
|
89
90
|
return tuple(advisories)
|
|
90
91
|
|
|
91
92
|
|
|
@@ -137,6 +138,15 @@ def dump_advisories(advisories: Iterable[Advisory]) -> str:
|
|
|
137
138
|
"severity": a.severity.value,
|
|
138
139
|
"affected_symbols": [str(s) for s in a.affected_symbols],
|
|
139
140
|
"aliases": list(a.aliases),
|
|
141
|
+
"entry_points": [
|
|
142
|
+
{
|
|
143
|
+
"symbol": str(e.symbol),
|
|
144
|
+
"justification": e.justification,
|
|
145
|
+
"conditions": [_condition_entry(c) for c in e.conditions],
|
|
146
|
+
}
|
|
147
|
+
for e in a.entry_points
|
|
148
|
+
],
|
|
149
|
+
"modules": list(a.modules),
|
|
140
150
|
}
|
|
141
151
|
for a in advisories
|
|
142
152
|
],
|
|
@@ -144,6 +154,15 @@ def dump_advisories(advisories: Iterable[Advisory]) -> str:
|
|
|
144
154
|
return json.dumps(document, indent=2) + "\n"
|
|
145
155
|
|
|
146
156
|
|
|
157
|
+
def _condition_entry(condition: Condition) -> dict[str, Any]:
|
|
158
|
+
entry: dict[str, Any] = {"kind": condition.kind, "text": condition.text}
|
|
159
|
+
if condition.argument is not None:
|
|
160
|
+
entry["argument"] = condition.argument
|
|
161
|
+
if condition.values:
|
|
162
|
+
entry["values"] = list(condition.values)
|
|
163
|
+
return entry
|
|
164
|
+
|
|
165
|
+
|
|
147
166
|
def load_advisories(path: Path) -> tuple[Advisory, ...]:
|
|
148
167
|
try:
|
|
149
168
|
document = json.loads(path.read_text(encoding="utf-8"))
|
|
@@ -165,4 +184,22 @@ def _advisory(entry: Mapping[str, Any]) -> Advisory:
|
|
|
165
184
|
Severity(entry["severity"]),
|
|
166
185
|
tuple(SymbolId(str(s)) for s in entry.get("affected_symbols") or []),
|
|
167
186
|
tuple(str(a) for a in entry.get("aliases") or []),
|
|
187
|
+
tuple(_entry_point(e) for e in entry.get("entry_points") or []),
|
|
188
|
+
tuple(str(m) for m in entry.get("modules") or []),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
|
|
193
|
+
return AdvisoryEntryPoint(
|
|
194
|
+
SymbolId(str(entry["symbol"])),
|
|
195
|
+
str(entry["justification"]),
|
|
196
|
+
tuple(
|
|
197
|
+
Condition(
|
|
198
|
+
str(c["kind"]),
|
|
199
|
+
str(c["text"]),
|
|
200
|
+
None if c.get("argument") is None else str(c["argument"]),
|
|
201
|
+
tuple(str(v) for v in c.get("values") or []),
|
|
202
|
+
)
|
|
203
|
+
for c in entry.get("conditions") or []
|
|
204
|
+
),
|
|
168
205
|
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Correlation engine (architecture §27).
|
|
2
|
+
|
|
3
|
+
A package required in a vulnerable version, an API the advisory affects, a call to that
|
|
4
|
+
API reachable in the project, and attacker-controlled data reaching that call: the
|
|
5
|
+
affected APIs become sinks of the ``ADVISORY`` taint kind, so the shared taint engine,
|
|
6
|
+
the function summaries and the refutation engine do the work, and the flows they leave
|
|
7
|
+
are correlated here into one high-confidence ``exploitable-vulnerability`` finding.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Iterable, Mapping
|
|
13
|
+
|
|
14
|
+
from coretrace_python.dependency.graph import DIRECT, Advisory, DependencyGraph
|
|
15
|
+
from coretrace_python.findings import Confidence, Finding, Severity
|
|
16
|
+
from coretrace_python.findings.refutation import Status, Verdict, Verdicts
|
|
17
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
18
|
+
from coretrace_python.taint import Sink, TaintFlow, TaintKind
|
|
19
|
+
|
|
20
|
+
Affected = Mapping[SymbolId, tuple[Advisory, ...]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def affected_symbols(dependencies: DependencyGraph, advisories: Iterable[Advisory]) -> Affected:
|
|
24
|
+
"""The APIs affected by advisories whose package is required in a vulnerable version,
|
|
25
|
+
each with every such advisory: a call to ``yaml.load`` reaches every CVE of the
|
|
26
|
+
pinned release, not the first one listed."""
|
|
27
|
+
|
|
28
|
+
affected: dict[SymbolId, list[Advisory]] = {}
|
|
29
|
+
for requirement in dependencies.requirements:
|
|
30
|
+
for advisory in advisories:
|
|
31
|
+
if advisory.affects(requirement):
|
|
32
|
+
for symbol in advisory.reachable_symbols:
|
|
33
|
+
found = affected.setdefault(symbol, [])
|
|
34
|
+
if advisory not in found:
|
|
35
|
+
found.append(advisory)
|
|
36
|
+
return {symbol: tuple(found) for symbol, found in affected.items()}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def advisory_sinks(affected: Affected) -> tuple[Sink, ...]:
|
|
40
|
+
return tuple(Sink(symbol, TaintKind.ADVISORY) for symbol in affected)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def evidence(advisory: Advisory, symbol: SymbolId, level: str) -> dict[str, str]:
|
|
44
|
+
"""What a finding keeps of the advisory for ``symbol``: the level of evidence
|
|
45
|
+
established, how the symbol relates to the vulnerability and under which
|
|
46
|
+
conditions, the ones the engine could not check listed as pending review."""
|
|
47
|
+
|
|
48
|
+
metadata = {"advisory": advisory.id, "package": advisory.package, "symbol": str(symbol), "level": level}
|
|
49
|
+
entry = advisory.entry_point(symbol)
|
|
50
|
+
if entry is None:
|
|
51
|
+
metadata["justification"] = DIRECT
|
|
52
|
+
return metadata
|
|
53
|
+
metadata["entry_point"] = str(entry.symbol)
|
|
54
|
+
metadata["justification"] = entry.justification
|
|
55
|
+
if entry.conditions:
|
|
56
|
+
metadata["conditions"] = "; ".join(c.text for c in entry.conditions)
|
|
57
|
+
# ponytail: no condition is checked yet, so every one awaits review; argument
|
|
58
|
+
# conditions get checked at the call site once call sites carry argument symbols.
|
|
59
|
+
metadata["conditions_pending_review"] = "; ".join(c.text for c in entry.conditions)
|
|
60
|
+
return metadata
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def correlate(
|
|
64
|
+
function: str,
|
|
65
|
+
flows: Iterable[TaintFlow],
|
|
66
|
+
verdicts: Verdicts | None,
|
|
67
|
+
affected: Affected,
|
|
68
|
+
) -> tuple[Finding, ...]:
|
|
69
|
+
"""Exploitable-vulnerability findings for the non-refuted ADVISORY flows of a function,
|
|
70
|
+
one per advisory the sink is affected by."""
|
|
71
|
+
|
|
72
|
+
findings: list[Finding] = []
|
|
73
|
+
for flow in flows:
|
|
74
|
+
if not flow.kinds & TaintKind.ADVISORY:
|
|
75
|
+
continue
|
|
76
|
+
verdict = verdicts.verdict(flow) if verdicts is not None else None
|
|
77
|
+
if verdict is not None and verdict.status is Status.REFUTED:
|
|
78
|
+
continue
|
|
79
|
+
hotspot = verdict is not None and verdict.status is Status.HOTSPOT
|
|
80
|
+
findings.extend(
|
|
81
|
+
_exploitable(function, flow, advisory, verdict, hotspot) for advisory in affected.get(flow.sink.symbol, ())
|
|
82
|
+
)
|
|
83
|
+
return tuple(findings)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _exploitable(
|
|
87
|
+
function: str, flow: TaintFlow, advisory: Advisory, verdict: Verdict | None, hotspot: bool
|
|
88
|
+
) -> Finding:
|
|
89
|
+
message = (
|
|
90
|
+
f"{advisory.id}: {flow.source.label} input reaches {flow.sink.symbol}, affected in "
|
|
91
|
+
f"the required {advisory.package} {advisory.vulnerable}: {advisory.summary}"
|
|
92
|
+
)
|
|
93
|
+
metadata = {
|
|
94
|
+
**evidence(advisory, flow.sink.symbol, "exploitable"),
|
|
95
|
+
"source": str(flow.source.symbol),
|
|
96
|
+
"source_label": flow.source.label,
|
|
97
|
+
"verdict": "hotspot" if hotspot else "vulnerability",
|
|
98
|
+
}
|
|
99
|
+
if verdict is not None:
|
|
100
|
+
metadata["evidence"] = verdict.evidence
|
|
101
|
+
if flow.through is not None and flow.sink_location is not None:
|
|
102
|
+
message += f" through {flow.through}"
|
|
103
|
+
metadata["through"] = flow.through
|
|
104
|
+
metadata["sink_line"] = str(flow.sink_location.start_line)
|
|
105
|
+
return Finding(
|
|
106
|
+
rule_id="exploitable-vulnerability",
|
|
107
|
+
message=message,
|
|
108
|
+
severity=Severity.CRITICAL,
|
|
109
|
+
confidence=Confidence.MEDIUM if hotspot else Confidence.HIGH,
|
|
110
|
+
span=flow.location,
|
|
111
|
+
function=function,
|
|
112
|
+
metadata=metadata,
|
|
113
|
+
)
|