coretrace-python-analyzer 0.8.0__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/CHANGELOG.md +18 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/usage.md +45 -4
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cli.py +7 -2
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/__init__.py +2 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/advisories.py +36 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/correlation.py +4 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/graph.py +34 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/sbom.py +23 -3
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/vex.py +8 -2
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/engine.py +88 -27
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/__init__.py +4 -0
- coretrace_python_analyzer-0.10.0/src/coretrace_python/findings/provenance.py +26 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/json_format.py +15 -5
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/report.py +3 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/sarif.py +13 -2
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/engine.py +17 -2
- coretrace_python_analyzer-0.10.0/tests/test_attacker_arguments.py +203 -0
- coretrace_python_analyzer-0.10.0/tests/test_provenance.py +232 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_release.py +1 -1
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/README.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/plugins.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Reports
|
|
6
|
+
|
|
7
|
+
- A directory check names what its result was produced with, besides the engine and the sources. It lists each plugin it loaded, by manifest name and version, and each advisory file it read, by path, each with the SHA-256 digest of its content. The formats show them in these places:
|
|
8
|
+
- JSON: `tool.components`;
|
|
9
|
+
- SARIF: `tool.extensions`;
|
|
10
|
+
- CycloneDX SBOM: `metadata.tools.components`, with their hashes;
|
|
11
|
+
- OpenVEX: the document's `tooling`, so its `@id` changes with the advisory data.
|
|
12
|
+
|
|
13
|
+
Every finding about an advisory names the plugin (`name@version`) or advisory file it comes from, in its `advisory_source` metadata. The cache key and the reports identify a plugin by the same digest (#149).
|
|
14
|
+
|
|
15
|
+
## 0.9.0 (2026-09-25)
|
|
16
|
+
|
|
17
|
+
### Precision
|
|
18
|
+
|
|
19
|
+
- An advisory entry point may name the `attacker_arguments` through which attacker input exploits it: by keyword, and by position when the argument may be passed positionally, a variadic one taking every later position. Attacker input in another argument leaves the call reachable instead of exploitable, so an advisory for `send_from_directory` that names its `path` no longer counts `download_name=`, and one for `requests.get` that names its `url` no longer counts the body. An argument unpacked with `*` or `**` may fill any of them: like an argument condition it leaves undecided, it does not rule the call out, so a wrapper forwarding `*args, **kwargs` still reaches the entry point. An entry point naming no argument counts every argument, as before. Each taint flow now records how its value is passed to the sink (#145).
|
|
20
|
+
|
|
3
21
|
## 0.8.0 (2026-09-25)
|
|
4
22
|
|
|
5
23
|
### Precision
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Static security analysis infrastructure for Python code
|
|
5
5
|
Project-URL: Homepage, https://github.com/CoreTrace/coretrace-python-analyzer
|
|
6
6
|
Project-URL: Repository, https://github.com/CoreTrace/coretrace-python-analyzer
|
|
@@ -239,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
239
239
|
```json
|
|
240
240
|
{
|
|
241
241
|
"schema_version": 1,
|
|
242
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
242
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.10.0"},
|
|
243
243
|
"root": "/home/me/project",
|
|
244
244
|
"findings": [
|
|
245
245
|
{
|
|
@@ -261,10 +261,34 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
261
261
|
|
|
262
262
|
`root` is the directory the paths are relative to.
|
|
263
263
|
|
|
264
|
+
A directory check also lists, in `tool.components`, what the result was produced with
|
|
265
|
+
besides the engine and the sources: each plugin it loaded, by manifest name and version,
|
|
266
|
+
and each advisory file it read, by path (relative to the root when under it). Each
|
|
267
|
+
component carries the SHA-256 digest of its content, so a result can be traced to the
|
|
268
|
+
exact advisory data it used and reproduced. A plugin's digest covers its directory as the
|
|
269
|
+
cache fingerprints it, bytecode and hidden files left out; a file's digest covers its
|
|
270
|
+
bytes. A single-file check lists no components.
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
"tool": {
|
|
274
|
+
"name": "coretrace-python-analyzer", "version": "0.10.0",
|
|
275
|
+
"components": [
|
|
276
|
+
{"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
|
|
277
|
+
{"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A finding about an advisory names, in its `advisory_source` metadata, where the advisory
|
|
283
|
+
comes from. For a plugin this is `name@version`, and for an advisory file its path. When
|
|
284
|
+
several sources define the same advisory, it names the one whose definition is used, as
|
|
285
|
+
the next section describes.
|
|
286
|
+
|
|
264
287
|
`--format sarif` prints a SARIF 2.1.0 log, one run with the tool, its rules and one
|
|
265
288
|
result per finding. The root is declared once as the `SRCROOT` original URI base and
|
|
266
289
|
every location under it is relative to that base, which is what code scanning services
|
|
267
|
-
need to attach results to files
|
|
290
|
+
need to attach results to files. The components of a directory check are the tool's
|
|
291
|
+
`extensions`, their kind and digest in `properties`:
|
|
268
292
|
|
|
269
293
|
```bash
|
|
270
294
|
coretrace-python-analyzer --check src/ --format sarif > report.sarif
|
|
@@ -336,6 +360,19 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
|
|
|
336
360
|
`request.form.get('name')` all read it. A function reading it several times is reported
|
|
337
361
|
once, where it first reads it.
|
|
338
362
|
|
|
363
|
+
Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
|
|
364
|
+
serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
|
|
365
|
+
point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
|
|
366
|
+
passed positionally, by `position`, counted as for argument conditions; `"variadic": true`
|
|
367
|
+
extends a position to every later one, as `safe_join(directory, *pathnames)` takes
|
|
368
|
+
(`{"position": 1, "variadic": true}`). A call is then exploitable only when attacker
|
|
369
|
+
input reaches one of them, `"attacker_arguments": [{"argument": "url", "position": 0}]`
|
|
370
|
+
for `requests.get`; attacker input in another argument leaves it reachable. An argument
|
|
371
|
+
unpacked with `*args` or `**kwargs` may fill any of them: like an argument condition it
|
|
372
|
+
leaves undecided, it does not rule the call out, so it counts, and a wrapper forwarding
|
|
373
|
+
`*args, **kwargs` to the entry point still reaches it. An entry point without
|
|
374
|
+
`attacker_arguments` is exploitable through any argument.
|
|
375
|
+
|
|
339
376
|
An `argument` condition is decided at each call. It names the argument by keyword and,
|
|
340
377
|
when it may be passed positionally, by `position` (counted from 0, the receiver of a
|
|
341
378
|
method excluded); `values` are what makes the call affected, written as the analyzer
|
|
@@ -372,7 +409,9 @@ ignore = ["CVE-2020-1747"]
|
|
|
372
409
|
```
|
|
373
410
|
|
|
374
411
|
`--sbom PATH` writes a CycloneDX 1.5 bill of materials: one component per requirement
|
|
375
|
-
with its package URL, and the advisories affecting them as vulnerabilities.
|
|
412
|
+
with its package URL, and the advisories affecting them as vulnerabilities. Its tools
|
|
413
|
+
list the analyzer and the components of the check, each with its SHA-256 hash: a plugin
|
|
414
|
+
as an `application`, an advisory file as `data`.
|
|
376
415
|
|
|
377
416
|
```bash
|
|
378
417
|
coretrace-python-analyzer --check src/ --sbom sbom.json --policy security/policy.toml
|
|
@@ -394,7 +433,9 @@ not the code of installed packages. A framework calling the vulnerable function
|
|
|
394
433
|
project's behalf would go unseen. Without a lock file, a package is never
|
|
395
434
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
396
435
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
397
|
-
cannot know it.
|
|
436
|
+
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
437
|
+
(`coretrace-python-analyzer 0.10.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
438
|
+
`@id` changes with the advisory data a statement was decided with.
|
|
398
439
|
|
|
399
440
|
## Large projects
|
|
400
441
|
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cli.py
RENAMED
|
@@ -242,9 +242,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
242
242
|
findings = analysis.findings
|
|
243
243
|
coverage = analysis.coverage
|
|
244
244
|
suppressed = analysis.suppressed
|
|
245
|
+
components = analysis.components
|
|
245
246
|
if args.sbom is not None:
|
|
246
247
|
args.sbom.write_text(
|
|
247
|
-
render_sbom(
|
|
248
|
+
render_sbom(
|
|
249
|
+
analysis.dependencies, analysis.advisories, engine.TOOL_NAME, __version__, analysis.components
|
|
250
|
+
),
|
|
248
251
|
encoding="utf-8",
|
|
249
252
|
)
|
|
250
253
|
if args.vex is not None:
|
|
@@ -258,6 +261,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
258
261
|
engine.TOOL_NAME,
|
|
259
262
|
__version__,
|
|
260
263
|
datetime.now(UTC),
|
|
264
|
+
analysis.components,
|
|
261
265
|
),
|
|
262
266
|
encoding="utf-8",
|
|
263
267
|
)
|
|
@@ -265,6 +269,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
265
269
|
file_analysis = engine.analyze_file(SourceManager().load_file(args.path), plugin_roots)
|
|
266
270
|
findings, coverage = file_analysis.findings, file_analysis.coverage
|
|
267
271
|
suppressed = file_analysis.suppressed
|
|
272
|
+
components = ()
|
|
268
273
|
root = args.path.resolve() if args.path.is_dir() else args.path.resolve().parent
|
|
269
274
|
baselined = None
|
|
270
275
|
if args.baseline is not None:
|
|
@@ -273,7 +278,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
273
278
|
else:
|
|
274
279
|
Baseline.of(findings, root).save(args.baseline)
|
|
275
280
|
findings, baselined = (), findings
|
|
276
|
-
report = engine.report(findings, coverage, root, suppressed, baselined)
|
|
281
|
+
report = engine.report(findings, coverage, root, suppressed, baselined, components)
|
|
277
282
|
print(render(args.format or "text", report), end="")
|
|
278
283
|
threshold = Severity(args.fail_on).rank if args.fail_on is not None else 0
|
|
279
284
|
failing = any(finding.severity.rank >= threshold for finding in findings)
|
|
@@ -12,6 +12,7 @@ from coretrace_python.dependency.graph import (
|
|
|
12
12
|
DEPENDENCY_FILES,
|
|
13
13
|
Advisory,
|
|
14
14
|
AdvisoryEntryPoint,
|
|
15
|
+
AttackerArgument,
|
|
15
16
|
Condition,
|
|
16
17
|
DependencyAnalysis,
|
|
17
18
|
DependencyGraph,
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"Advisory",
|
|
32
33
|
"AdvisoryEntryPoint",
|
|
33
34
|
"AdvisoryFileError",
|
|
35
|
+
"AttackerArgument",
|
|
34
36
|
"Condition",
|
|
35
37
|
"DependencyAnalysis",
|
|
36
38
|
"DependencyGraph",
|
|
@@ -17,7 +17,13 @@ from collections.abc import Iterable, Iterator, Mapping
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from coretrace_python.dependency.graph import
|
|
20
|
+
from coretrace_python.dependency.graph import (
|
|
21
|
+
Advisory,
|
|
22
|
+
AdvisoryEntryPoint,
|
|
23
|
+
AttackerArgument,
|
|
24
|
+
Condition,
|
|
25
|
+
normalize,
|
|
26
|
+
)
|
|
21
27
|
from coretrace_python.findings import Severity
|
|
22
28
|
from coretrace_python.semantic.symbols import SymbolId
|
|
23
29
|
|
|
@@ -155,6 +161,19 @@ def _entry_point_entry(entry_point: AdvisoryEntryPoint) -> dict[str, Any]:
|
|
|
155
161
|
}
|
|
156
162
|
if entry_point.read:
|
|
157
163
|
entry["read"] = True
|
|
164
|
+
if entry_point.attacker_arguments:
|
|
165
|
+
entry["attacker_arguments"] = [_attacker_argument_entry(a) for a in entry_point.attacker_arguments]
|
|
166
|
+
return entry
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _attacker_argument_entry(argument: AttackerArgument) -> dict[str, Any]:
|
|
170
|
+
entry: dict[str, Any] = {}
|
|
171
|
+
if argument.argument is not None:
|
|
172
|
+
entry["argument"] = argument.argument
|
|
173
|
+
if argument.position is not None:
|
|
174
|
+
entry["position"] = argument.position
|
|
175
|
+
if argument.variadic:
|
|
176
|
+
entry["variadic"] = True
|
|
158
177
|
return entry
|
|
159
178
|
|
|
160
179
|
|
|
@@ -206,9 +225,25 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
|
|
|
206
225
|
str(entry["justification"]),
|
|
207
226
|
tuple(_condition(c) for c in entry.get("conditions") or []),
|
|
208
227
|
read,
|
|
228
|
+
tuple(_attacker_argument(a) for a in entry.get("attacker_arguments") or []),
|
|
209
229
|
)
|
|
210
230
|
|
|
211
231
|
|
|
232
|
+
def _attacker_argument(entry: Mapping[str, Any]) -> AttackerArgument:
|
|
233
|
+
argument, position, variadic = entry.get("argument"), entry.get("position"), entry.get("variadic", False)
|
|
234
|
+
if argument is not None and not isinstance(argument, str):
|
|
235
|
+
raise TypeError(f"attacker argument name must be a string, got {argument!r}")
|
|
236
|
+
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
237
|
+
raise TypeError(f"attacker argument position must be a non-negative integer, got {position!r}")
|
|
238
|
+
if not isinstance(variadic, bool):
|
|
239
|
+
raise TypeError(f"attacker argument variadic must be true or false, got {variadic!r}")
|
|
240
|
+
if argument is None and position is None:
|
|
241
|
+
raise ValueError("an attacker argument needs a name, a position or both")
|
|
242
|
+
if variadic and position is None:
|
|
243
|
+
raise ValueError("a variadic attacker argument needs the position it starts at")
|
|
244
|
+
return AttackerArgument(argument, position, variadic)
|
|
245
|
+
|
|
246
|
+
|
|
212
247
|
def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
213
248
|
position = entry.get("position")
|
|
214
249
|
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
@@ -133,7 +133,10 @@ def correlate(
|
|
|
133
133
|
continue
|
|
134
134
|
hotspot = verdict is not None and verdict.status is Status.HOTSPOT
|
|
135
135
|
for advisory in affected.get(flow.sink.symbol, ()):
|
|
136
|
-
|
|
136
|
+
entry = advisory.entry_point(flow.sink.symbol)
|
|
137
|
+
if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
|
|
138
|
+
continue
|
|
139
|
+
check = check_conditions(entry, flow.sink_arguments)
|
|
137
140
|
if check.contradicted is None:
|
|
138
141
|
findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
|
|
139
142
|
return tuple(findings)
|
|
@@ -166,6 +166,31 @@ class Condition:
|
|
|
166
166
|
return self.kind == "argument"
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class AttackerArgument:
|
|
171
|
+
"""An argument of an entry point that must carry the attacker's input: passed by
|
|
172
|
+
keyword ``argument``, or at ``position`` (from 0, a method's receiver excluded) when it
|
|
173
|
+
may be passed positionally; a ``variadic`` one, such as ``*pathnames``, also takes every
|
|
174
|
+
later position."""
|
|
175
|
+
|
|
176
|
+
argument: str | None = None
|
|
177
|
+
position: int | None = None
|
|
178
|
+
variadic: bool = False
|
|
179
|
+
|
|
180
|
+
def receives(self, position: int | None, keyword: str | None) -> bool:
|
|
181
|
+
"""Whether the argument passed at ``position`` or as ``keyword`` is this one. An
|
|
182
|
+
argument unpacked with ``*`` or ``**`` has neither and may fill this one: like an
|
|
183
|
+
argument condition it leaves undecided, it does not rule the call out, so it is."""
|
|
184
|
+
|
|
185
|
+
if keyword is not None:
|
|
186
|
+
return keyword == self.argument
|
|
187
|
+
if position is None:
|
|
188
|
+
return True
|
|
189
|
+
if self.position is None:
|
|
190
|
+
return False
|
|
191
|
+
return position == self.position or (self.variadic and position > self.position)
|
|
192
|
+
|
|
193
|
+
|
|
169
194
|
@dataclass(frozen=True)
|
|
170
195
|
class AdvisoryEntryPoint:
|
|
171
196
|
"""A public API through which a project reaches an affected symbol, justified by the
|
|
@@ -173,12 +198,20 @@ class AdvisoryEntryPoint:
|
|
|
173
198
|
|
|
174
199
|
An entry point is reached by a call, or — when ``read`` is set, for an attribute whose
|
|
175
200
|
getter runs the affected code, such as a lazily parsed request body — by any read of
|
|
176
|
-
it or of an attribute of it.
|
|
201
|
+
it or of an attribute of it. When it names ``attacker_arguments``, attacker input
|
|
202
|
+
exploits it only through one of them; otherwise through any argument."""
|
|
177
203
|
|
|
178
204
|
symbol: SymbolId
|
|
179
205
|
justification: str
|
|
180
206
|
conditions: tuple[Condition, ...] = ()
|
|
181
207
|
read: bool = False
|
|
208
|
+
attacker_arguments: tuple[AttackerArgument, ...] = ()
|
|
209
|
+
|
|
210
|
+
def exploitable_through(self, position: int | None, keyword: str | None) -> bool:
|
|
211
|
+
"""Whether attacker input in the argument at ``position`` or ``keyword`` can
|
|
212
|
+
exploit the vulnerability."""
|
|
213
|
+
|
|
214
|
+
return not self.attacker_arguments or any(a.receives(position, keyword) for a in self.attacker_arguments)
|
|
182
215
|
|
|
183
216
|
|
|
184
217
|
DIRECT = "affected symbol, changed by the fix"
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from collections.abc import Iterable
|
|
6
|
+
from collections.abc import Iterable, Sequence
|
|
7
7
|
|
|
8
8
|
from coretrace_python.dependency.graph import Advisory, DependencyGraph, Requirement
|
|
9
|
+
from coretrace_python.findings import PLUGIN, Component
|
|
9
10
|
|
|
10
11
|
SPEC_VERSION = "1.5"
|
|
11
12
|
|
|
@@ -33,8 +34,22 @@ def _component(requirement: Requirement) -> dict[str, object]:
|
|
|
33
34
|
return component
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def _tool(component: Component) -> dict[str, object]:
|
|
38
|
+
"""A plugin as an application, an advisory file as data, each with its SHA-256."""
|
|
39
|
+
|
|
40
|
+
tool: dict[str, object] = {"type": "application" if component.kind == PLUGIN else "data", "name": component.name}
|
|
41
|
+
if component.version is not None:
|
|
42
|
+
tool["version"] = component.version
|
|
43
|
+
tool["hashes"] = [{"alg": "SHA-256", "content": component.digest.removeprefix("sha256:")}]
|
|
44
|
+
return tool
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
def render_sbom(
|
|
37
|
-
dependencies: DependencyGraph,
|
|
48
|
+
dependencies: DependencyGraph,
|
|
49
|
+
advisories: Iterable[Advisory],
|
|
50
|
+
tool_name: str,
|
|
51
|
+
tool_version: str,
|
|
52
|
+
components: Sequence[Component] = (),
|
|
38
53
|
) -> str:
|
|
39
54
|
"""A CycloneDX JSON document: one component per requirement, and the advisories
|
|
40
55
|
affecting them as vulnerabilities. Deterministic for a given graph."""
|
|
@@ -57,7 +72,12 @@ def render_sbom(
|
|
|
57
72
|
"specVersion": SPEC_VERSION,
|
|
58
73
|
"version": 1,
|
|
59
74
|
"metadata": {
|
|
60
|
-
"tools": {
|
|
75
|
+
"tools": {
|
|
76
|
+
"components": [
|
|
77
|
+
{"type": "application", "name": tool_name, "version": tool_version},
|
|
78
|
+
*(_tool(component) for component in components),
|
|
79
|
+
]
|
|
80
|
+
}
|
|
61
81
|
},
|
|
62
82
|
"components": [_component(requirement) for requirement in requirements],
|
|
63
83
|
"vulnerabilities": vulnerabilities,
|
|
@@ -23,7 +23,7 @@ from urllib.parse import quote
|
|
|
23
23
|
|
|
24
24
|
from coretrace_python.dependency.graph import Advisory, DependencyGraph, Requirement
|
|
25
25
|
from coretrace_python.dependency.sbom import purl
|
|
26
|
-
from coretrace_python.findings import Finding
|
|
26
|
+
from coretrace_python.findings import Component, Finding
|
|
27
27
|
from coretrace_python.findings.coverage import Coverage
|
|
28
28
|
|
|
29
29
|
CONTEXT = "https://openvex.dev/ns/v0.2.0"
|
|
@@ -44,6 +44,7 @@ def render_vex(
|
|
|
44
44
|
tool_name: str,
|
|
45
45
|
tool_version: str,
|
|
46
46
|
timestamp: datetime,
|
|
47
|
+
components: Sequence[Component] = (),
|
|
47
48
|
) -> str:
|
|
48
49
|
"""The OpenVEX document of the project at ``root``. ``evidence`` is every finding of
|
|
49
50
|
the check, those suppressed or accepted by the policy included. The document's
|
|
@@ -69,7 +70,12 @@ def render_vex(
|
|
|
69
70
|
**_status(advisory, requirement, about, dependencies, coverage, root),
|
|
70
71
|
}
|
|
71
72
|
)
|
|
72
|
-
tooling =
|
|
73
|
+
tooling = "; ".join(
|
|
74
|
+
(
|
|
75
|
+
f"{tool_name} {tool_version}",
|
|
76
|
+
*(" ".join(filter(None, (c.name, c.version, f"({c.digest})"))) for c in components),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
73
79
|
content = {
|
|
74
80
|
"@context": CONTEXT,
|
|
75
81
|
"author": AUTHOR,
|
{coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -7,9 +7,10 @@ plugins and analyses never import it.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import concurrent.futures
|
|
10
|
+
import hashlib
|
|
10
11
|
import multiprocessing
|
|
11
12
|
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
12
|
-
from dataclasses import dataclass, field
|
|
13
|
+
from dataclasses import dataclass, field, replace
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from types import MappingProxyType
|
|
15
16
|
from typing import Any, ClassVar
|
|
@@ -55,7 +56,10 @@ from coretrace_python.dependency.correlation import (
|
|
|
55
56
|
correlate,
|
|
56
57
|
)
|
|
57
58
|
from coretrace_python.findings import (
|
|
59
|
+
ADVISORIES,
|
|
58
60
|
FINDING_SCHEMA_VERSION,
|
|
61
|
+
PLUGIN,
|
|
62
|
+
Component,
|
|
59
63
|
Confidence,
|
|
60
64
|
Coverage,
|
|
61
65
|
FileCoverage,
|
|
@@ -217,6 +221,8 @@ class ProjectAnalysis:
|
|
|
217
221
|
suppressed: tuple[Finding, ...] = ()
|
|
218
222
|
# Findings about an advisory the policy accepts, still evidence of what is reached.
|
|
219
223
|
accepted: tuple[Finding, ...] = ()
|
|
224
|
+
# The plugins and advisory files the result was produced with.
|
|
225
|
+
components: tuple[Component, ...] = ()
|
|
220
226
|
|
|
221
227
|
|
|
222
228
|
@dataclass(frozen=True)
|
|
@@ -358,10 +364,10 @@ def analyze_project(
|
|
|
358
364
|
for error in dependencies.errors:
|
|
359
365
|
findings.append(_note("syntax-error", error, sources.add_source(error.split(":")[0], ""), 1))
|
|
360
366
|
advisory_paths = _advisory_paths(root, advisory_files)
|
|
361
|
-
file_advisories: list[Advisory] = []
|
|
367
|
+
file_advisories: list[tuple[Path, tuple[Advisory, ...]]] = []
|
|
362
368
|
for path in advisory_paths:
|
|
363
369
|
try:
|
|
364
|
-
file_advisories.
|
|
370
|
+
file_advisories.append((path, load_advisories(path)))
|
|
365
371
|
except AdvisoryFileError as error:
|
|
366
372
|
findings.append(_note("syntax-error", str(error), sources.add_source(str(path), ""), 1))
|
|
367
373
|
policy = Policy()
|
|
@@ -392,9 +398,8 @@ def analyze_project(
|
|
|
392
398
|
probe = next(iter(managers.values()), None) or _register_all(build_hir(sources.add_source("<empty>", "")))
|
|
393
399
|
registry = load_plugins(plugin_roots, probe)
|
|
394
400
|
all_plugins: tuple[Plugin, ...] = (*(loaded.plugin for loaded in registry), *plugins)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
)
|
|
401
|
+
components = _components(registry, root, (path for path, _ in file_advisories))
|
|
402
|
+
advisories, origins = _merge_advisories(_contributions(registry, plugins, root, file_advisories))
|
|
398
403
|
affected = affected_symbols(dependencies, advisories)
|
|
399
404
|
models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
|
|
400
405
|
for manager in managers.values():
|
|
@@ -425,7 +430,7 @@ def analyze_project(
|
|
|
425
430
|
manager.provide(EscapedTemplates, escaped)
|
|
426
431
|
manager.provide(ClearingAnalysis, clearing)
|
|
427
432
|
|
|
428
|
-
configuration = _configuration_key(
|
|
433
|
+
configuration = _configuration_key(components, plugins, models, advisories, dependencies, routes, escaped)
|
|
429
434
|
keys = module_keys(
|
|
430
435
|
graph,
|
|
431
436
|
{name: fingerprint(configuration, str(files[name].source_id), name, files[name].text) for name in analysable},
|
|
@@ -511,6 +516,7 @@ def analyze_project(
|
|
|
511
516
|
for plugin in all_plugins:
|
|
512
517
|
if isinstance(plugin, ProjectPlugin):
|
|
513
518
|
findings = list(apply_refinement(plugin, findings, plugin.refine(context, tuple(findings))))
|
|
519
|
+
findings = [_sourced(finding, origins) for finding in findings]
|
|
514
520
|
accepted = tuple(f for f in findings if policy.accepts(f))
|
|
515
521
|
kept, suppressed = partition(apply_policy(policy, findings), _text_of(sources))
|
|
516
522
|
return ProjectAnalysis(
|
|
@@ -524,20 +530,78 @@ def analyze_project(
|
|
|
524
530
|
Coverage(tuple(sorted(coverage, key=lambda c: c.path))),
|
|
525
531
|
suppressed,
|
|
526
532
|
accepted,
|
|
533
|
+
components,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
Origins = Mapping[tuple[str, str], str]
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _merge_advisories(contributions: Iterable[tuple[str, Iterable[Advisory]]]) -> tuple[tuple[Advisory, ...], Origins]:
|
|
541
|
+
"""One advisory per identifier and package, with the contributor it comes from: a
|
|
542
|
+
later contributor replaces an earlier one, so a curated plugin refines a bundled
|
|
543
|
+
sample and a local file, the project's own feed, wins over every plugin."""
|
|
544
|
+
|
|
545
|
+
merged: dict[tuple[str, str], tuple[str, Advisory]] = {}
|
|
546
|
+
for source, advisories in contributions:
|
|
547
|
+
for advisory in advisories:
|
|
548
|
+
merged[(advisory.id, advisory.package)] = (source, advisory)
|
|
549
|
+
return (
|
|
550
|
+
tuple(advisory for _, advisory in merged.values()),
|
|
551
|
+
MappingProxyType({key: source for key, (source, _) in merged.items()}),
|
|
527
552
|
)
|
|
528
553
|
|
|
529
554
|
|
|
530
|
-
def
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
555
|
+
def _contributions(
|
|
556
|
+
registry: PluginRegistry,
|
|
557
|
+
plugins: Sequence[Plugin],
|
|
558
|
+
root: Path,
|
|
559
|
+
files: Iterable[tuple[Path, tuple[Advisory, ...]]],
|
|
560
|
+
) -> list[tuple[str, Iterable[Advisory]]]:
|
|
561
|
+
"""The advisories of each plugin, then of each advisory file, each under the name a
|
|
562
|
+
finding cites it by: ``name@version`` for a loaded plugin, the path for a file."""
|
|
563
|
+
|
|
564
|
+
return [
|
|
565
|
+
*((f"{loaded.manifest.name}@{loaded.manifest.version}", loaded.plugin.advisories) for loaded in registry),
|
|
566
|
+
*((plugin.name, plugin.advisories) for plugin in plugins),
|
|
567
|
+
*((_located(path, root), advisories) for path, advisories in files),
|
|
568
|
+
]
|
|
536
569
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
570
|
+
|
|
571
|
+
def _components(registry: PluginRegistry, root: Path, files: Iterable[Path]) -> tuple[Component, ...]:
|
|
572
|
+
"""The plugins and advisory files a project result is produced with, each with the
|
|
573
|
+
digest of its content: the plugin directory as the cache fingerprints it, the file's
|
|
574
|
+
bytes."""
|
|
575
|
+
|
|
576
|
+
return (
|
|
577
|
+
*(
|
|
578
|
+
Component(PLUGIN, loaded.manifest.name, f"sha256:{directory_fingerprint(loaded.directory)}", loaded.manifest.version)
|
|
579
|
+
for loaded in registry
|
|
580
|
+
),
|
|
581
|
+
*(
|
|
582
|
+
Component(ADVISORIES, _located(path, root), f"sha256:{hashlib.sha256(path.read_bytes()).hexdigest()}")
|
|
583
|
+
for path in files
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _located(path: Path, root: Path) -> str:
|
|
589
|
+
"""``path`` relative to ``root``, POSIX style, when it lies under it."""
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
return path.resolve().relative_to(root.resolve()).as_posix()
|
|
593
|
+
except ValueError:
|
|
594
|
+
return str(path)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _sourced(finding: Finding, origins: Origins) -> Finding:
|
|
598
|
+
"""``finding`` with the plugin or advisory file its advisory comes from, when it
|
|
599
|
+
cites one."""
|
|
600
|
+
|
|
601
|
+
origin = origins.get((finding.metadata.get("advisory", ""), finding.metadata.get("package", "")))
|
|
602
|
+
if origin is None:
|
|
603
|
+
return finding
|
|
604
|
+
return replace(finding, metadata={**finding.metadata, "advisory_source": origin})
|
|
541
605
|
|
|
542
606
|
|
|
543
607
|
def _advisory_paths(root: Path, advisory_files: Sequence[Path]) -> tuple[Path, ...]:
|
|
@@ -623,15 +687,13 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
|
623
687
|
registry = load_plugins(batch.plugin_roots, next(iter(managers.values())))
|
|
624
688
|
all_plugins: tuple[Plugin, ...] = (*(loaded.plugin for loaded in registry), *batch.plugins)
|
|
625
689
|
dependencies = resolve_dependencies(batch.root, sources)
|
|
626
|
-
file_advisories: list[Advisory] = []
|
|
690
|
+
file_advisories: list[tuple[Path, tuple[Advisory, ...]]] = []
|
|
627
691
|
for path in batch.advisory_paths:
|
|
628
692
|
try:
|
|
629
|
-
file_advisories.
|
|
693
|
+
file_advisories.append((path, load_advisories(path)))
|
|
630
694
|
except AdvisoryFileError:
|
|
631
695
|
continue
|
|
632
|
-
advisories = _merge_advisories(
|
|
633
|
-
(a for plugin in all_plugins for a in plugin.advisories), file_advisories
|
|
634
|
-
)
|
|
696
|
+
advisories, _ = _merge_advisories(_contributions(registry, batch.plugins, batch.root, file_advisories))
|
|
635
697
|
affected = affected_symbols(dependencies, advisories)
|
|
636
698
|
models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
|
|
637
699
|
routes = _decode_routes(batch.routes)
|
|
@@ -663,7 +725,7 @@ def _decode_routes(data: tuple[tuple[str, str, str, int], ...]) -> dict[SymbolId
|
|
|
663
725
|
|
|
664
726
|
|
|
665
727
|
def _configuration_key(
|
|
666
|
-
|
|
728
|
+
components: Sequence[Component],
|
|
667
729
|
plugins: Sequence[Plugin],
|
|
668
730
|
models: ModelTable,
|
|
669
731
|
advisories: tuple[Advisory, ...],
|
|
@@ -679,10 +741,7 @@ def _configuration_key(
|
|
|
679
741
|
str(PYIR_SCHEMA_VERSION),
|
|
680
742
|
str(FINDING_SCHEMA_VERSION),
|
|
681
743
|
str(PLUGIN_API_VERSION),
|
|
682
|
-
*(
|
|
683
|
-
f"{loaded.manifest.name}={loaded.manifest.version}:{directory_fingerprint(loaded.directory)}"
|
|
684
|
-
for loaded in registry
|
|
685
|
-
),
|
|
744
|
+
*(f"{c.name}={c.version}:{c.digest}" for c in components if c.kind == PLUGIN),
|
|
686
745
|
*(f"{type(p).__module__}.{type(p).__qualname__}" for p in plugins),
|
|
687
746
|
repr(models),
|
|
688
747
|
repr(advisories),
|
|
@@ -776,6 +835,7 @@ def report(
|
|
|
776
835
|
root: Path | None = None,
|
|
777
836
|
suppressed: Sequence[Finding] = (),
|
|
778
837
|
baselined: Sequence[Finding] | None = None,
|
|
838
|
+
components: Sequence[Component] = (),
|
|
779
839
|
) -> Report:
|
|
780
840
|
"""The report of a check; paths under ``root`` are rendered relative to it.
|
|
781
841
|
``baselined`` is the findings a baseline accounts for, ``None`` when none applied."""
|
|
@@ -789,4 +849,5 @@ def report(
|
|
|
789
849
|
tuple(suppressed),
|
|
790
850
|
tuple(baselined or ()),
|
|
791
851
|
baselined is not None,
|
|
852
|
+
tuple(components),
|
|
792
853
|
)
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from coretrace_python.findings.coverage import Coverage, FileCoverage
|
|
4
4
|
from coretrace_python.findings.model import FINDING_SCHEMA_VERSION, Confidence, Finding, Severity
|
|
5
|
+
from coretrace_python.findings.provenance import ADVISORIES, PLUGIN, Component
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
8
|
+
"ADVISORIES",
|
|
7
9
|
"FINDING_SCHEMA_VERSION",
|
|
10
|
+
"PLUGIN",
|
|
11
|
+
"Component",
|
|
8
12
|
"Confidence",
|
|
9
13
|
"Coverage",
|
|
10
14
|
"FileCoverage",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""What a result was produced with, besides the engine and the analysed sources.
|
|
2
|
+
|
|
3
|
+
Advisories decide the dependency findings and the VEX statements, and they come from
|
|
4
|
+
plugins and advisory files that change on their own schedule. A report names each of
|
|
5
|
+
them with the digest of its content, so a result can be traced to its data and
|
|
6
|
+
reproduced.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
PLUGIN = "plugin"
|
|
14
|
+
ADVISORIES = "advisories"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Component:
|
|
19
|
+
"""A loaded plugin, by its manifest ``name`` and ``version``, or an advisory file, by
|
|
20
|
+
its path as ``name``. ``digest`` is ``sha256:`` and the hex digest of its content:
|
|
21
|
+
the plugin directory as the cache fingerprints it, or the file's bytes."""
|
|
22
|
+
|
|
23
|
+
kind: str
|
|
24
|
+
name: str
|
|
25
|
+
digest: str
|
|
26
|
+
version: str | None = None
|