coretrace-python-analyzer 0.3.0__tar.gz → 0.5.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.3.0 → coretrace_python_analyzer-0.5.0}/CHANGELOG.md +29 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/docs/plugins.md +39 -4
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/docs/usage.md +56 -4
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/__init__.py +1 -1
- coretrace_python_analyzer-0.5.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +49 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +2 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +1 -1
- coretrace_python_analyzer-0.5.0/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +71 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cache.py +37 -8
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dependency/__init__.py +4 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dependency/advisories.py +55 -5
- coretrace_python_analyzer-0.5.0/src/coretrace_python/dependency/correlation.py +181 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dependency/graph.py +79 -5
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/engine.py +26 -11
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/interprocedural/__init__.py +4 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/interprocedural/callgraph.py +71 -11
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/interprocedural/modulegraph.py +4 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/interprocedural/summaries.py +8 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/lowering.py +57 -3
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/__init__.py +4 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/api.py +65 -5
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/loader.py +4 -4
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/semantic/scopes.py +3 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/taint/__init__.py +2 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/taint/engine.py +85 -16
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/Chatbot.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/DjangoGoat.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/Flask-Image-Editing-Website.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/InsecureGram.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/MiniBookApiServer.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/NPL-IA.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/Spam-Detection-Project.json +2 -2
- coretrace_python_analyzer-0.5.0/tests/regression/expected/Vulnerable_fastapi.json +136 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/dvpwa.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/full-stack-fastapi-template.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/healthchecks.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/httpie.json +8 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/luigi.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/my_gdb.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/network-topologer.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/pygoat.json +8 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/vulnerable-flask.json +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/expected/yt-fts.json +2 -2
- coretrace_python_analyzer-0.5.0/tests/test_advisory_evidence.py +225 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_advisory_feeds.py +3 -2
- coretrace_python_analyzer-0.5.0/tests/test_argument_conditions.py +217 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_baseline.py +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_cache.py +15 -4
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_class_keywords_with_targets.py +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_closures.py +5 -5
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_dependencies.py +13 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_discovery.py +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_heap.py +1 -1
- coretrace_python_analyzer-0.5.0/tests/test_module_level.py +146 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_plugins.py +26 -3
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_project.py +157 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_pyir_imports.py +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_release.py +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_remaining_limits.py +1 -1
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_syntax_control_flow.py +2 -2
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_syntax_coverage.py +4 -2
- coretrace_python_analyzer-0.3.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -56
- coretrace_python_analyzer-0.3.0/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -43
- coretrace_python_analyzer-0.3.0/src/coretrace_python/dependency/correlation.py +0 -87
- coretrace_python_analyzer-0.3.0/tests/regression/expected/Vulnerable_fastapi.json +0 -46
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/README.md +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/taint/models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/tests/test_tornado_bottle_models.py +0 -0
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-09-24)
|
|
4
|
+
|
|
5
|
+
### Dependencies
|
|
6
|
+
|
|
7
|
+
- Argument conditions of advisory entry points are decided at each call, with a `position` for positional arguments and a `default` for absent ones: a call meeting them is reported with `conditions_met`, a call passing another known value does not reach the vulnerability and is listed in the requirement's `ruled_out`, and a call the analyzer cannot decide leaves them pending review (#110).
|
|
8
|
+
|
|
9
|
+
### Plugins
|
|
10
|
+
|
|
11
|
+
- `ProjectContext.functions(module)` gives project plugins every function of a module as its call graph names it, with its span and the label of the entry point it is (`http` for a route, `argv` for a command), decided by the taint engine's own rule; modules served from the cache keep them, and the cache format is bumped (#116).
|
|
12
|
+
- Call sites and external calls record what each argument denotes — a symbol, a constant as Python writes it, or nothing known — in an `Arguments` record: `CallSite.arguments` (formerly the argument counts), `ExternalCall.arguments`, and `TaintFlow.sink_arguments` for the sink call of a flow, through callees in other modules too (#110).
|
|
13
|
+
|
|
14
|
+
## 0.4.0 (2026-09-24)
|
|
15
|
+
|
|
16
|
+
### Analysis
|
|
17
|
+
|
|
18
|
+
- 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).
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
- 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).
|
|
23
|
+
- A call to an API affected by several advisories of the pinned release reports every one of them, not the first listed (#109).
|
|
24
|
+
- 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).
|
|
25
|
+
- 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).
|
|
26
|
+
|
|
27
|
+
### Plugins
|
|
28
|
+
|
|
29
|
+
- 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).
|
|
30
|
+
- `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).
|
|
31
|
+
|
|
3
32
|
## 0.3.0 (2026-09-22)
|
|
4
33
|
|
|
5
34
|
### Plugins
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -162,6 +162,38 @@ symbols, as the shipped `sample-advisories` plugin does. Requirements matching t
|
|
|
162
162
|
reported, calls to the affected symbols become reachable vulnerabilities and tainted
|
|
163
163
|
calls become exploitable ones.
|
|
164
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
|
+
|
|
165
197
|
### Secret scanners: `SecretDetector`
|
|
166
198
|
|
|
167
199
|
Judges every string literal of the module, or every value of a configuration file,
|
|
@@ -190,9 +222,12 @@ rule. Findings carry a redacted preview only.
|
|
|
190
222
|
|
|
191
223
|
Runs once per project after every module is analysed, with a `ProjectContext`: the
|
|
192
224
|
module graph, the dependency graph, every advisory the plugins and advisory files
|
|
193
|
-
contributed, the policy, the project root, and each module's imports
|
|
194
|
-
|
|
195
|
-
|
|
225
|
+
contributed, the policy, the project root, and each module's imports, call graph and
|
|
226
|
+
functions. `ctx.functions(module)` gives every function as the call graph names it
|
|
227
|
+
(`Admin.get`, `outer.inner`, `<module>`), with its span, so a finding can be placed in
|
|
228
|
+
one, and the label of the entry point it is (`http` for a route, `argv` for a command)
|
|
229
|
+
when the models make it one. The shipped `vulnerable-dependency`,
|
|
230
|
+
`reachable-vulnerability` and `dependency-policy` plugins are of this kind.
|
|
196
231
|
|
|
197
232
|
```python
|
|
198
233
|
from collections.abc import Sequence
|
|
@@ -206,7 +241,7 @@ class RequirementsPresent(ProjectPlugin):
|
|
|
206
241
|
name: ClassVar[str] = "requirements-present"
|
|
207
242
|
|
|
208
243
|
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
209
|
-
return () # inspect ctx.dependencies, ctx.modules(), ctx.
|
|
244
|
+
return () # inspect ctx.dependencies, ctx.modules, ctx.call_graph(module), ctx.functions(module)
|
|
210
245
|
```
|
|
211
246
|
|
|
212
247
|
### Anything else: `Plugin`
|
|
@@ -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.5.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,61 @@ 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, the default", "argument": "Loader", "position": 1,
|
|
275
|
+
"values": ["python.yaml.FullLoader", "python.yaml.loader.FullLoader"], "default": true},
|
|
276
|
+
{"kind": "semantic", "text": "the document carries a python/object/apply tag"}
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
"modules": ["yaml"],
|
|
258
281
|
"aliases": ["GHSA-8q59-q68h-6hv4"]
|
|
259
282
|
}
|
|
260
283
|
]
|
|
261
284
|
}
|
|
262
285
|
```
|
|
263
286
|
|
|
287
|
+
`affected_symbols` are the functions the fix changed; `entry_points` the public APIs
|
|
288
|
+
through which a project reaches them, each with the justification that ties it to the
|
|
289
|
+
fix — the commit, or the call path — and the `conditions` under which it is affected. A
|
|
290
|
+
call to either kind of symbol is reachable. `modules` names the top-level modules the
|
|
291
|
+
package installs, so the analyzer can tell an imported package from a merely required
|
|
292
|
+
one; it defaults to the package name.
|
|
293
|
+
|
|
294
|
+
An `argument` condition is decided at each call. It names the argument by keyword and,
|
|
295
|
+
when it may be passed positionally, by `position` (counted from 0, the receiver of a
|
|
296
|
+
method excluded); `values` are what makes the call affected, written as the analyzer
|
|
297
|
+
sees arguments: a symbol by its canonical name (`python.yaml.FullLoader`, and every
|
|
298
|
+
spelling a project may use, such as `python.yaml.loader.FullLoader`), a constant as
|
|
299
|
+
Python writes it (`True`, `None`, `'/static'`); `default` says an absent argument means
|
|
300
|
+
an affected value. A call passing another known value does not reach the vulnerability;
|
|
301
|
+
a call passing a variable, or unpacking `*args` or `**kwargs`, leaves the condition
|
|
302
|
+
pending review. A `semantic` condition — the document's tags, the platform, the
|
|
303
|
+
template's origin — cannot be decided and is always pending review, never dropped.
|
|
304
|
+
|
|
305
|
+
Every dependency finding records the highest level of evidence established in its
|
|
306
|
+
`level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
|
|
307
|
+
(a module of the package is imported somewhere), `reachable` (an entry point or affected
|
|
308
|
+
symbol is called) or `exploitable` (attacker input reaches it). The last two carry
|
|
309
|
+
`entry_point`, `justification`, `conditions`, and which of them the call meets
|
|
310
|
+
(`conditions_met`) or leaves to review (`conditions_pending_review`). A requirement whose
|
|
311
|
+
entry points are only called with arguments that contradict a condition stays
|
|
312
|
+
`imported`, and its finding lists those calls in `ruled_out`
|
|
313
|
+
(`app.views:12 python.yaml.load(Loader=python.yaml.SafeLoader)`), the evidence that
|
|
314
|
+
they do not reach the vulnerability.
|
|
315
|
+
|
|
264
316
|
A `coretrace-policy.toml` at the root, or the file passed with `--policy`, denies
|
|
265
317
|
packages, requires pins and lists accepted advisories whose findings are dropped:
|
|
266
318
|
|
|
@@ -0,0 +1,49 @@
|
|
|
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, check_conditions, 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
|
+
check = check_conditions(advisory.entry_point(site.target.symbol), site.arguments)
|
|
33
|
+
if check.contradicted is not None:
|
|
34
|
+
continue
|
|
35
|
+
findings.append(
|
|
36
|
+
Finding(
|
|
37
|
+
rule_id="reachable-vulnerability",
|
|
38
|
+
message=(
|
|
39
|
+
f"{advisory.id}: {site.target.symbol} is affected in the required "
|
|
40
|
+
f"{advisory.package} {advisory.vulnerable}: {advisory.summary}"
|
|
41
|
+
),
|
|
42
|
+
severity=advisory.severity,
|
|
43
|
+
confidence=Confidence.HIGH,
|
|
44
|
+
span=site.location,
|
|
45
|
+
function=function,
|
|
46
|
+
metadata=evidence(advisory, site.target.symbol, "reachable", check),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
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
|
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Requirements that allow a version an advisory marks as vulnerable."""
|
|
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, check_conditions, ruled_out
|
|
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 VulnerableDependencyPlugin(ProjectPlugin):
|
|
17
|
+
name: ClassVar[str] = "vulnerable-dependency"
|
|
18
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
|
|
19
|
+
|
|
20
|
+
def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
|
|
21
|
+
findings: list[Finding] = []
|
|
22
|
+
imported = [s for module in ctx.modules for s in ctx.imports(module).all_symbols()]
|
|
23
|
+
excluded = _ruled_out(ctx)
|
|
24
|
+
for requirement in ctx.dependencies.requirements:
|
|
25
|
+
for advisory in ctx.advisories:
|
|
26
|
+
if not advisory.affects(requirement):
|
|
27
|
+
continue
|
|
28
|
+
pinned = requirement.pinned is not None
|
|
29
|
+
level = "imported" if advisory.imported_by(imported) else "declared"
|
|
30
|
+
metadata = {
|
|
31
|
+
"advisory": advisory.id,
|
|
32
|
+
"package": advisory.package,
|
|
33
|
+
"specifier": requirement.specifier,
|
|
34
|
+
"level": level,
|
|
35
|
+
}
|
|
36
|
+
if excluded.get(advisory.id):
|
|
37
|
+
metadata["ruled_out"] = "; ".join(excluded[advisory.id])
|
|
38
|
+
findings.append(
|
|
39
|
+
Finding(
|
|
40
|
+
rule_id="vulnerable-dependency",
|
|
41
|
+
message=(
|
|
42
|
+
f"{advisory.id}: {advisory.package} {advisory.vulnerable} is "
|
|
43
|
+
f"{'required' if pinned else 'allowed'} by {requirement.name}"
|
|
44
|
+
f"{requirement.specifier or ''}: {advisory.summary}"
|
|
45
|
+
),
|
|
46
|
+
severity=advisory.severity,
|
|
47
|
+
confidence=Confidence.HIGH if pinned else Confidence.MEDIUM,
|
|
48
|
+
span=requirement.span,
|
|
49
|
+
metadata=metadata,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return findings
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _ruled_out(ctx: ProjectContext) -> dict[str, list[str]]:
|
|
56
|
+
"""By advisory, the calls to its entry points whose arguments contradict one of its
|
|
57
|
+
conditions: the evidence that the requirement, imported, is not reached there."""
|
|
58
|
+
|
|
59
|
+
affected = affected_symbols(ctx.dependencies, ctx.advisories)
|
|
60
|
+
excluded: dict[str, list[str]] = {}
|
|
61
|
+
for module in sorted(ctx.modules):
|
|
62
|
+
graph = ctx.call_graph(module)
|
|
63
|
+
for function in graph.functions:
|
|
64
|
+
for site in graph.sites(function):
|
|
65
|
+
if not isinstance(site.target, ExternalSymbol):
|
|
66
|
+
continue
|
|
67
|
+
for advisory in affected.get(site.target.symbol, ()):
|
|
68
|
+
check = check_conditions(advisory.entry_point(site.target.symbol), site.arguments)
|
|
69
|
+
if check.contradicted is not None:
|
|
70
|
+
excluded.setdefault(advisory.id, []).append(ruled_out(module, site, check))
|
|
71
|
+
return excluded
|
{coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.5.0}/src/coretrace_python/cache.py
RENAMED
|
@@ -22,11 +22,13 @@ from typing import Any
|
|
|
22
22
|
|
|
23
23
|
from coretrace_python.findings import Confidence, Finding, Severity
|
|
24
24
|
from coretrace_python.interprocedural import (
|
|
25
|
+
Arguments,
|
|
25
26
|
CallSite,
|
|
26
27
|
ExternalCall,
|
|
27
28
|
ExternalSymbol,
|
|
28
29
|
FunctionSummary,
|
|
29
30
|
KnownFunction,
|
|
31
|
+
ModuleFunction,
|
|
30
32
|
ModuleGraph,
|
|
31
33
|
Mutation,
|
|
32
34
|
NonlocalWrite,
|
|
@@ -37,14 +39,14 @@ from coretrace_python.interprocedural import (
|
|
|
37
39
|
from coretrace_python.semantic.symbols import SymbolId
|
|
38
40
|
from coretrace_python.source import SourceId, SourceSpan
|
|
39
41
|
|
|
40
|
-
CACHE_FORMAT =
|
|
42
|
+
CACHE_FORMAT = 7
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
@dataclass(frozen=True)
|
|
44
46
|
class CachedModule:
|
|
45
47
|
"""Everything a later run needs from one module without lowering it again."""
|
|
46
48
|
|
|
47
|
-
functions: tuple[
|
|
49
|
+
functions: tuple[ModuleFunction, ...]
|
|
48
50
|
summaries: Mapping[str, FunctionSummary]
|
|
49
51
|
sites: tuple[CallSite, ...]
|
|
50
52
|
findings: tuple[Finding, ...]
|
|
@@ -128,7 +130,7 @@ class ProjectCache:
|
|
|
128
130
|
def encode(module: CachedModule) -> dict[str, Any]:
|
|
129
131
|
return {
|
|
130
132
|
"format": CACHE_FORMAT,
|
|
131
|
-
"functions":
|
|
133
|
+
"functions": [[f.name, _encode_span(f.span), f.entry_point] for f in module.functions],
|
|
132
134
|
"summaries": {name: _encode_summary(s) for name, s in module.summaries.items()},
|
|
133
135
|
"sites": [_encode_site(site) for site in module.sites],
|
|
134
136
|
"findings": [_encode_finding(finding) for finding in module.findings],
|
|
@@ -139,7 +141,7 @@ def decode(data: Mapping[str, Any]) -> CachedModule:
|
|
|
139
141
|
if data["format"] != CACHE_FORMAT:
|
|
140
142
|
raise ValueError(f"unsupported cache format {data['format']!r}")
|
|
141
143
|
return CachedModule(
|
|
142
|
-
tuple(
|
|
144
|
+
tuple(_decode_function(function) for function in data["functions"]),
|
|
143
145
|
{_string(name): _decode_summary(s) for name, s in data["summaries"].items()},
|
|
144
146
|
tuple(_decode_site(site) for site in data["sites"]),
|
|
145
147
|
tuple(_decode_finding(finding) for finding in data["findings"]),
|
|
@@ -187,6 +189,11 @@ def _decode_span(data: Any) -> SourceSpan:
|
|
|
187
189
|
)
|
|
188
190
|
|
|
189
191
|
|
|
192
|
+
def _decode_function(data: Any) -> ModuleFunction:
|
|
193
|
+
name, span, entry_point = data
|
|
194
|
+
return ModuleFunction(_string(name), _decode_span(span), None if entry_point is None else _string(entry_point))
|
|
195
|
+
|
|
196
|
+
|
|
190
197
|
def _encode_finding(finding: Finding) -> dict[str, Any]:
|
|
191
198
|
return {
|
|
192
199
|
"rule": finding.rule_id,
|
|
@@ -219,6 +226,7 @@ def _encode_call(call: ExternalCall) -> dict[str, Any]:
|
|
|
219
226
|
"keywords": sorted(call.keyword_dependencies),
|
|
220
227
|
"location": _encode_span(call.location),
|
|
221
228
|
"call_site": None if call.call_site is None else _encode_span(call.call_site),
|
|
229
|
+
"given": _encode_arguments(call.arguments),
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
|
|
@@ -230,6 +238,7 @@ def _decode_call(data: Mapping[str, Any]) -> ExternalCall:
|
|
|
230
238
|
_indices(data["keywords"]),
|
|
231
239
|
_decode_span(data["location"]),
|
|
232
240
|
None if site is None else _decode_span(site),
|
|
241
|
+
_decode_arguments(data["given"]),
|
|
233
242
|
)
|
|
234
243
|
|
|
235
244
|
|
|
@@ -311,8 +320,7 @@ def _encode_site(site: CallSite) -> dict[str, Any]:
|
|
|
311
320
|
"caller": site.caller,
|
|
312
321
|
"location": _encode_span(site.location),
|
|
313
322
|
"target": _encode_target(site.target),
|
|
314
|
-
"arguments": site.arguments,
|
|
315
|
-
"keywords": site.keywords,
|
|
323
|
+
"arguments": _encode_arguments(site.arguments),
|
|
316
324
|
}
|
|
317
325
|
|
|
318
326
|
|
|
@@ -321,6 +329,27 @@ def _decode_site(data: Mapping[str, Any]) -> CallSite:
|
|
|
321
329
|
_string(data["caller"]),
|
|
322
330
|
_decode_span(data["location"]),
|
|
323
331
|
_decode_target(data["target"]),
|
|
324
|
-
|
|
325
|
-
|
|
332
|
+
_decode_arguments(data["arguments"]),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _encode_arguments(arguments: Arguments) -> dict[str, Any]:
|
|
337
|
+
return {
|
|
338
|
+
"positional": list(arguments.positional),
|
|
339
|
+
"keywords": [[name, value] for name, value in arguments.keywords],
|
|
340
|
+
"unpacked": arguments.unpacked,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _decode_arguments(data: Mapping[str, Any]) -> Arguments:
|
|
345
|
+
def denoted(value: Any) -> str | None:
|
|
346
|
+
return None if value is None else _string(value)
|
|
347
|
+
|
|
348
|
+
unpacked = data["unpacked"]
|
|
349
|
+
if not isinstance(unpacked, bool):
|
|
350
|
+
raise TypeError(f"expected a boolean, got {unpacked!r}")
|
|
351
|
+
return Arguments(
|
|
352
|
+
tuple(denoted(value) for value in data["positional"]),
|
|
353
|
+
tuple((_string(name), denoted(value)) for name, value in data["keywords"]),
|
|
354
|
+
unpacked,
|
|
326
355
|
)
|
|
@@ -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,19 @@ 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
|
+
if condition.position is not None:
|
|
164
|
+
entry["position"] = condition.position
|
|
165
|
+
if condition.default:
|
|
166
|
+
entry["default"] = True
|
|
167
|
+
return entry
|
|
168
|
+
|
|
169
|
+
|
|
147
170
|
def load_advisories(path: Path) -> tuple[Advisory, ...]:
|
|
148
171
|
try:
|
|
149
172
|
document = json.loads(path.read_text(encoding="utf-8"))
|
|
@@ -165,4 +188,31 @@ def _advisory(entry: Mapping[str, Any]) -> Advisory:
|
|
|
165
188
|
Severity(entry["severity"]),
|
|
166
189
|
tuple(SymbolId(str(s)) for s in entry.get("affected_symbols") or []),
|
|
167
190
|
tuple(str(a) for a in entry.get("aliases") or []),
|
|
191
|
+
tuple(_entry_point(e) for e in entry.get("entry_points") or []),
|
|
192
|
+
tuple(str(m) for m in entry.get("modules") or []),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
|
|
197
|
+
return AdvisoryEntryPoint(
|
|
198
|
+
SymbolId(str(entry["symbol"])),
|
|
199
|
+
str(entry["justification"]),
|
|
200
|
+
tuple(_condition(c) for c in entry.get("conditions") or []),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
205
|
+
position = entry.get("position")
|
|
206
|
+
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
207
|
+
raise TypeError(f"condition position must be a non-negative integer, got {position!r}")
|
|
208
|
+
default = entry.get("default", False)
|
|
209
|
+
if not isinstance(default, bool):
|
|
210
|
+
raise TypeError(f"condition default must be true or false, got {default!r}")
|
|
211
|
+
return Condition(
|
|
212
|
+
str(entry["kind"]),
|
|
213
|
+
str(entry["text"]),
|
|
214
|
+
None if entry.get("argument") is None else str(entry["argument"]),
|
|
215
|
+
tuple(str(v) for v in entry.get("values") or []),
|
|
216
|
+
position,
|
|
217
|
+
default,
|
|
168
218
|
)
|