coretrace-python-analyzer 0.15.0__tar.gz → 0.17.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.15.0 → coretrace_python_analyzer-0.17.0}/CHANGELOG.md +14 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/plugins.md +6 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/usage.md +64 -4
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +9 -2
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/django/django_models.py +4 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/vex.py +10 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/engine.py +34 -3
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/coverage.py +4 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/__init__.py +12 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/callgraph.py +52 -7
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/summaries.py +179 -7
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/api.py +7 -4
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/__init__.py +18 -1
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/engine.py +97 -11
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/models.py +62 -2
- coretrace_python_analyzer-0.17.0/src/coretrace_python/taint/templates.py +690 -0
- coretrace_python_analyzer-0.17.0/tests/test_base_patterns.py +326 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.17.0/tests/test_template_context_flows.py +306 -0
- coretrace_python_analyzer-0.17.0/tests/test_template_filter_calls.py +283 -0
- coretrace_python_analyzer-0.17.0/tests/test_template_request_processor.py +215 -0
- coretrace_python_analyzer-0.15.0/src/coretrace_python/taint/templates.py +0 -250
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/README.md +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/strings.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/advisories.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/correlation.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/graph.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/provenance.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/urls.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_url_host.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_attacker_arguments.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_code_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_members.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_open_redirect_destinations.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_provenance.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssrf_destinations.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_url_proof.py +0 -0
- {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 (2026-09-28)
|
|
4
|
+
|
|
5
|
+
### Plugins
|
|
6
|
+
|
|
7
|
+
- `BasePattern(pattern, label, parameters, kinds)` makes the methods of a class whose base's canonical symbol matches a regular expression entry points, for bases generated per project such as a gRPC servicer's, whatever the module's prefix and however the base is imported. Each parameter after `self` is, by position, either input of the model's kinds or an object of a class the model names, which the sources on that class taint without the object being input: a `Source` on `ServicerContext.invocation_metadata` gives the client's metadata, and `peer()` gives nothing. When the project holds the base, only the methods the base defines are entry points, so a servicer's utility methods are not; otherwise every method with the listed number of parameters is. `async` methods and the messages of an iterated parameter are covered (#176).
|
|
8
|
+
|
|
9
|
+
## 0.16.0 (2026-09-27)
|
|
10
|
+
|
|
11
|
+
### Advisories
|
|
12
|
+
|
|
13
|
+
- A filter of Django's own libraries that a project template applies is a call to the function behind it, at the template's line: `{{ bio|striptags }}` calls `django.template.defaultfilters.striptags`, and so do filters in tag arguments and `{% filter %}` blocks. An advisory entry point naming that function is reachable there, whether or not a Python call names the template, since a class-based view renders its `template_name` inside Django. A template the analyzer cannot read may apply any filter: when the project names one by an expression, or by a name found under no `templates` directory, in `render`, `render_to_string`, `TemplateResponse`, `{% include %}` or `{% extends %}`, an advisory whose entry points include a template filter stays `under_investigation` in the VEX document, and the notes say where. `render`, `TemplateResponse` and `SimpleTemplateResponse` are now template renders like `render_to_string` (#146).
|
|
14
|
+
- Attacker input in a render context reaching a template filter makes an advisory entry point naming that filter exploitable at the render call. The analyzer links them only where it is certain of the value: a dict literal with constant keys that the render call alone uses, and a filter the rendered template applies to a context variable, directly or through `{% for %}`, `{% with %}`, constant `{% include %}` and `{% extends %}`, earlier filter arguments and `{% filter %}` blocks, in the blocks that render. A name a tag binds shadows the context, and `{% include ... only %}` passes none of it. The filter's value is its first argument, the filter's own argument its second. A project function rendering what it receives carries it the same way (#146).
|
|
15
|
+
- The request the `request` context processor adds reaches a template's filters too, as `{{ request.GET.q|striptags }}`, only where the analyzer is certain of it: every Django template engine the settings assign to `TEMPLATES`, as literals no other code names, lists the processor; the render call passes the request (`render`, `TemplateResponse`, `render_to_string` given `request`) with a certain context, absent or a dict literal; nothing shadows `request`; and the template reads a text attribute of the request object, such as `GET`, `POST`, `COOKIES` or `headers`. Otherwise the filter stays reachable (#146).
|
|
16
|
+
|
|
3
17
|
## 0.15.0 (2026-09-26)
|
|
4
18
|
|
|
5
19
|
### Precision
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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
|
|
@@ -154,10 +154,11 @@ class BottleModels(ModelPlugin):
|
|
|
154
154
|
| `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
|
|
155
155
|
| `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
|
|
156
156
|
| `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
|
|
157
|
+
| `BasePattern(pattern, label, parameters=(None,), kinds=ALL)` | A regular expression on the canonical symbol of a class's base, for bases generated per project such as a gRPC servicer's (`(?:^|\.)\w+_pb2_grpc\.\w+Servicer$`), whatever the module's prefix and however the base is imported. The methods taking the parameters listed, by position after `self`, are entry points: a position left `None` is `label` input carrying `kinds`; one holding a class symbol denotes an object of that class, which the sources on the class taint, as `invocation_metadata()` on a `ServicerContext`, without the object itself being input. When the project holds the base, only the methods the base defines count; otherwise every method with that number of parameters. Inheritance identifies a potential service, not its registration on a server. |
|
|
157
158
|
| `RouteRegistrar(symbol, argument, label, kinds=ALL, keyword=None)` | A call registering a view at argument `argument` (or `keyword`), such as Django's `path`; the view receives `label` input. |
|
|
158
159
|
| `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
|
|
159
160
|
| `SafeArgument(symbol, argument, values, position=None, kinds=ALL)` | A call to the sink `symbol` whose `argument` (by keyword, or at `position`) denotes one of `values` is not a sink for `kinds`, such as `yaml.load` with `Loader=yaml.SafeLoader`. Values are written as the analyzer records arguments: a symbol by its canonical name, a constant as Python writes it (`True`). Only a value given explicitly makes the call safe. |
|
|
160
|
-
| `TemplateRender(symbol, position=0, keyword="template_name")` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string
|
|
161
|
+
| `TemplateRender(symbol, position=0, keyword="template_name", context=1, request=None)` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string`, with the variables of its argument at `context` or `context=` and the request at `request` or `request=` (None: the call takes none); it makes the filter calls the template applies, fed from that context and, with the `request` context processor, that request (see the usage guide). What it returns carries no `HTML` when the name is written in the call and the project's template escapes everything it prints (see the usage guide). A name the analyzer cannot read keeps an advisory a template filter reaches under investigation in the VEX document. |
|
|
161
162
|
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe for `kinds`, such as `re.fullmatch`. A flow guarded by it is refuted when it carries no other kind into the sink, and is a hotspot otherwise: a redirect validator does not make a shell command safe. `symbol` may be a function of the project, by its project symbol (`python.hc.accounts.views._allow_redirect`), which then counts wherever it is called, in its own module too. |
|
|
162
163
|
| `RequestObject(symbol, text=())` | The inputs from `symbol`, an entry point, a route registrar, a typed parameter or a source, are request objects. A view receives the request first (after `self`), then URL parameters, which are text. Reading one of the request's `text` attributes gives text, as Django's `GET` or aiohttp's `query`; its body and files keep the request's kinds. An attribute read counts only when every source of the value is such a request. |
|
|
163
164
|
| `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
|
|
@@ -258,6 +259,10 @@ one, and the label of the entry point it is (`http` for a route, `argv` for a co
|
|
|
258
259
|
when the models make it one. `ctx.call_graph(module)` gives each function's call sites
|
|
259
260
|
(`sites(function)`) and the symbols it reads, called or not (`reads(function)`: reading
|
|
260
261
|
`request.form['name']` reads `python.flask.request.form`), each where it first reads it.
|
|
262
|
+
`ctx.templates` gives the templates found under the project's `templates` directories:
|
|
263
|
+
their `names`, the filters they apply as `calls` (each a `FilterCall(symbol, span)`, the
|
|
264
|
+
function behind the filter and where the template applies it), and where they name a
|
|
265
|
+
template the analyzer cannot read (`unread`).
|
|
261
266
|
The shipped `vulnerable-dependency`, `reachable-vulnerability` and `dependency-policy`
|
|
262
267
|
plugins are of this kind.
|
|
263
268
|
|
|
@@ -241,7 +241,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
241
241
|
```json
|
|
242
242
|
{
|
|
243
243
|
"schema_version": 1,
|
|
244
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
244
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.17.0"},
|
|
245
245
|
"root": "/home/me/project",
|
|
246
246
|
"findings": [
|
|
247
247
|
{
|
|
@@ -273,7 +273,7 @@ bytes. A single-file check lists no components.
|
|
|
273
273
|
|
|
274
274
|
```json
|
|
275
275
|
"tool": {
|
|
276
|
-
"name": "coretrace-python-analyzer", "version": "0.
|
|
276
|
+
"name": "coretrace-python-analyzer", "version": "0.17.0",
|
|
277
277
|
"components": [
|
|
278
278
|
{"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
|
|
279
279
|
{"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
|
|
@@ -362,6 +362,57 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
|
|
|
362
362
|
`request.form.get('name')` all read it. A function reading it several times is reported
|
|
363
363
|
once, where it first reads it.
|
|
364
364
|
|
|
365
|
+
A filter of Django's own libraries that a project template applies is a call to the
|
|
366
|
+
function behind it, placed at the template's line: `{{ bio|striptags }}` calls
|
|
367
|
+
`python.django.template.defaultfilters.striptags`, and so do filters in tag arguments
|
|
368
|
+
(`{% if bio|striptags %}`) and `{% filter %}` blocks. A template counts whether or not a
|
|
369
|
+
Python call names it, since a class-based view renders its `template_name` inside
|
|
370
|
+
Django. Most filters are functions of the same name in `django.template.defaultfilters`;
|
|
371
|
+
`escape`, `escapejs`, `linebreaks`, `phone2numeric`, `slice`, `timesince` and `timeuntil`
|
|
372
|
+
are `<name>_filter` there, and the filters of `i18n`, `l10n`, `tz` and `humanize` are in
|
|
373
|
+
their library's module (`timezone` is `django.templatetags.tz.do_timezone`).
|
|
374
|
+
|
|
375
|
+
Attacker input in the render context makes the filter exploitable at the render call:
|
|
376
|
+
`render(request, "app/profile.html", {"bio": request.POST["bio"]})` with
|
|
377
|
+
`{{ bio|striptags }}`. The analyzer links a context value to a filter only where it is
|
|
378
|
+
certain of it:
|
|
379
|
+
|
|
380
|
+
- The context is a dict literal with constant keys, used by the render call and
|
|
381
|
+
nothing else, written in the call or through a variable. A dict mutated or passed
|
|
382
|
+
elsewhere, one unpacking another (`**extra`), or a context built any other way leaves
|
|
383
|
+
the filter reachable.
|
|
384
|
+
- The filter's value is a context variable, directly or through `{% for %}` and
|
|
385
|
+
`{% with %}`, the templates the template includes or extends by a constant name, the
|
|
386
|
+
arguments of the filters before it, or the output of a `{% filter %}` block. A name a
|
|
387
|
+
tag binds shadows the context, and `{% include ... only %}` passes none of it.
|
|
388
|
+
- The filter renders: a template extending another renders only its blocks, which
|
|
389
|
+
replace the blocks of that name in what it extends.
|
|
390
|
+
- One file has the template's name; of two files of the same name, which one renders
|
|
391
|
+
depends on the loaders.
|
|
392
|
+
|
|
393
|
+
The filter's value is its first argument and its argument (`cut:bio`) its second, so an
|
|
394
|
+
entry point naming only the value in `attacker_arguments` is not exploitable through
|
|
395
|
+
the argument. A project function rendering the values it receives carries them the same
|
|
396
|
+
way.
|
|
397
|
+
|
|
398
|
+
With the `django.template.context_processors.request` context processor, a template
|
|
399
|
+
rendered with the request reads it as `request`: `{{ request.GET.q|striptags }}` is
|
|
400
|
+
exploitable at the render call too. The analyzer counts it only where all of these hold:
|
|
401
|
+
|
|
402
|
+
- The project's settings assign `TEMPLATES` a literal list, every `DjangoTemplates`
|
|
403
|
+
engine of it lists the processor in a literal `context_processors`, and no other code
|
|
404
|
+
of the project names `TEMPLATES`. A mere mention of the processor proves nothing.
|
|
405
|
+
- The render call passes the request: `render` and `TemplateResponse` always do,
|
|
406
|
+
`render_to_string` when given `request`, `SimpleTemplateResponse` never.
|
|
407
|
+
- The render's context is certain, absent or a dict literal as above, so it cannot hide
|
|
408
|
+
a `request` entry. An entry of that name, a name a tag binds or `{% include ... only %}`
|
|
409
|
+
shadows the processor's request.
|
|
410
|
+
- The template reads text the user controls: an attribute the request object lists
|
|
411
|
+
among its text attributes, such as `GET`, `POST`, `COOKIES` or `headers`; not `user` or
|
|
412
|
+
`session`.
|
|
413
|
+
|
|
414
|
+
Through a project function, only the context entries are followed.
|
|
415
|
+
|
|
365
416
|
Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
|
|
366
417
|
serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
|
|
367
418
|
point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
|
|
@@ -444,16 +495,25 @@ check, including findings the policy accepts or a comment suppresses:
|
|
|
444
495
|
| Status | When |
|
|
445
496
|
|---|---|
|
|
446
497
|
| `affected` | The project's code reaches the vulnerable code. The notes give each place and its level, `reachable` or `exploitable`. |
|
|
447
|
-
| `not_affected` | Justified as `vulnerable_code_not_in_execute_path`, only when all of these hold: the advisory names entry points, no code of the project reaches them, every file and function was analysed, and a lock file (`uv.lock` or `poetry.lock`) shows that no other package requires the vulnerable one. The impact statement names the entry points and any calls ruled out by their arguments. |
|
|
498
|
+
| `not_affected` | Justified as `vulnerable_code_not_in_execute_path`, only when all of these hold: the advisory names entry points, no code of the project reaches them, every file and function was analysed, every template the project names was read if a template filter is among them, and a lock file (`uv.lock` or `poetry.lock`) shows that no other package requires the vulnerable one. The impact statement names the entry points and any calls ruled out by their arguments. |
|
|
448
499
|
| `under_investigation` | Anything else. The notes say which condition failed. |
|
|
449
500
|
|
|
501
|
+
A template the analyzer cannot read may apply any filter. The project names one when
|
|
502
|
+
`render`, `render_to_string`, `TemplateResponse`, an `{% include %}` or an `{% extends %}`
|
|
503
|
+
names a template by an expression, or by a name found under no `templates` directory,
|
|
504
|
+
including one an installed package ships; a template file that cannot be read counts
|
|
505
|
+
too. An advisory whose entry points include a template filter then stays
|
|
506
|
+
`under_investigation`, and the notes list where the project names each such template
|
|
507
|
+
(`app.views:12 names a template by an expression`). A template named elsewhere, such as
|
|
508
|
+
a class-based view's `template_name`, is not checked.
|
|
509
|
+
|
|
450
510
|
`not_affected` needs the lock-file check because the analyzer reads the project's code,
|
|
451
511
|
not the code of installed packages. A framework calling the vulnerable function on the
|
|
452
512
|
project's behalf would go unseen. Without a lock file, a package is never
|
|
453
513
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
454
514
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
455
515
|
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
456
|
-
(`coretrace-python-analyzer 0.
|
|
516
|
+
(`coretrace-python-analyzer 0.17.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
457
517
|
`@id` changes with the advisory data a statement was decided with.
|
|
458
518
|
|
|
459
519
|
## Large projects
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Calls, anywhere in the project, to an API affected by a vulnerable requirement, and
|
|
2
|
-
reads of an affected attribute whose getter runs the vulnerable code.
|
|
2
|
+
reads of an affected attribute whose getter runs the vulnerable code. A filter a project
|
|
3
|
+
template applies is a call to the function behind it, at the template's line."""
|
|
3
4
|
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
@@ -56,6 +57,10 @@ class ReachableVulnerabilityPlugin(ProjectPlugin):
|
|
|
56
57
|
reported.add((advisory, symbol))
|
|
57
58
|
check = check_conditions(entry, None)
|
|
58
59
|
findings.append(_reached(advisory, symbol, read.location, function, check))
|
|
60
|
+
for call in ctx.templates.calls:
|
|
61
|
+
for advisory in affected.get(call.symbol, ()):
|
|
62
|
+
check = check_conditions(advisory.entry_point(call.symbol), None)
|
|
63
|
+
findings.append(_reached(advisory, call.symbol, call.span, None, check))
|
|
59
64
|
return findings
|
|
60
65
|
|
|
61
66
|
|
|
@@ -67,7 +72,9 @@ def _read_through(symbol: SymbolId) -> Iterator[SymbolId]:
|
|
|
67
72
|
yield SymbolId(".".join(components[:end]))
|
|
68
73
|
|
|
69
74
|
|
|
70
|
-
def _reached(
|
|
75
|
+
def _reached(
|
|
76
|
+
advisory: Advisory, symbol: SymbolId, span: SourceSpan, function: str | None, check: ConditionCheck
|
|
77
|
+
) -> Finding:
|
|
71
78
|
return Finding(
|
|
72
79
|
rule_id="reachable-vulnerability",
|
|
73
80
|
message=(
|
|
@@ -139,7 +139,10 @@ class DjangoModels(ModelPlugin):
|
|
|
139
139
|
Sink(_sym("django.http.HttpResponseRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
|
|
140
140
|
Sink(_sym("django.http.HttpResponsePermanentRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
|
|
141
141
|
Sanitizer(_sym("django.utils.html.escape"), TaintKind.HTML),
|
|
142
|
-
TemplateRender(_sym("django.template.loader.render_to_string")),
|
|
142
|
+
TemplateRender(_sym("django.template.loader.render_to_string"), request=2),
|
|
143
|
+
TemplateRender(_sym("django.shortcuts.render"), 1, context=2, request=0),
|
|
144
|
+
TemplateRender(_sym("django.template.response.TemplateResponse"), 1, "template", 2, 0),
|
|
145
|
+
TemplateRender(_sym("django.template.response.SimpleTemplateResponse"), 0, "template"),
|
|
143
146
|
Sanitizer(_sym("django.utils.html.conditional_escape"), TaintKind.HTML),
|
|
144
147
|
# The masked CSRF secret: ASCII letters and digits only, a malformed cookie is
|
|
145
148
|
# replaced before it is used.
|
|
@@ -7,7 +7,8 @@ subcomponent of the project:
|
|
|
7
7
|
exploitable finding, whatever the policy or a suppression did with it;
|
|
8
8
|
- ``not_affected``, as ``vulnerable_code_not_in_execute_path``, only when the advisory is
|
|
9
9
|
curated (it names entry points), no code of the project reaches them, every file and
|
|
10
|
-
function was analysed,
|
|
10
|
+
function was analysed, every template the project names was read when a template
|
|
11
|
+
filter can reach them, and a lock file shows that no other package requires the
|
|
11
12
|
vulnerable one, since the code of installed packages is not analysed;
|
|
12
13
|
- ``under_investigation`` otherwise, with the reason in its notes.
|
|
13
14
|
"""
|
|
@@ -25,6 +26,7 @@ from coretrace_python.dependency.graph import Advisory, DependencyGraph, Require
|
|
|
25
26
|
from coretrace_python.dependency.sbom import purl
|
|
26
27
|
from coretrace_python.findings import Component, Finding
|
|
27
28
|
from coretrace_python.findings.coverage import Coverage
|
|
29
|
+
from coretrace_python.taint import FILTER_FUNCTIONS
|
|
28
30
|
|
|
29
31
|
CONTEXT = "https://openvex.dev/ns/v0.2.0"
|
|
30
32
|
# OpenVEX's shared namespace for documents without an IRI of their own, and the author
|
|
@@ -33,6 +35,8 @@ NAMESPACE = "https://openvex.dev/docs/public/vex-"
|
|
|
33
35
|
AUTHOR = "Unknown Author"
|
|
34
36
|
# Evidence levels that put the vulnerable code in the project's execution path, lowest first.
|
|
35
37
|
REACHED = ("reachable", "exploitable")
|
|
38
|
+
# What a template calls: a template the engine cannot read may reach these.
|
|
39
|
+
_TEMPLATE_CALLS = frozenset(FILTER_FUNCTIONS.values())
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
def render_vex(
|
|
@@ -140,6 +144,11 @@ def _status(
|
|
|
140
144
|
f"No analysed code reaches {entries}, but {', '.join(partial)} could not be fully "
|
|
141
145
|
f"analysed.{ruled}"
|
|
142
146
|
)
|
|
147
|
+
if coverage.unread_templates and _TEMPLATE_CALLS.intersection(advisory.reachable_symbols):
|
|
148
|
+
return _investigating(
|
|
149
|
+
f"No analysed code reaches {entries}, but a template can reach them, and the engine could "
|
|
150
|
+
f"not read every template the project names: {'; '.join(coverage.unread_templates)}.{ruled}"
|
|
151
|
+
)
|
|
143
152
|
required_by = dependencies.required_by(package)
|
|
144
153
|
if required_by is None:
|
|
145
154
|
return _investigating(
|
{coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -80,9 +80,12 @@ from coretrace_python.interprocedural import (
|
|
|
80
80
|
ModuleFunction,
|
|
81
81
|
ModuleGraph,
|
|
82
82
|
ProjectSummaries,
|
|
83
|
+
SignaturesAnalysis,
|
|
83
84
|
SummaryAnalysis,
|
|
84
85
|
SummaryIndex,
|
|
85
86
|
SymbolRead,
|
|
87
|
+
TemplateCalls,
|
|
88
|
+
TemplateCallsAnalysis,
|
|
86
89
|
build_module_graph,
|
|
87
90
|
discover_sources,
|
|
88
91
|
project_symbol,
|
|
@@ -124,7 +127,10 @@ from coretrace_python.taint import (
|
|
|
124
127
|
TaintAnalysis,
|
|
125
128
|
TaintKind,
|
|
126
129
|
escaped_templates,
|
|
130
|
+
project_templates,
|
|
127
131
|
registered_routes,
|
|
132
|
+
request_processor,
|
|
133
|
+
unread_renders,
|
|
128
134
|
)
|
|
129
135
|
from coretrace_python.taint.urls import flow_url
|
|
130
136
|
|
|
@@ -158,6 +164,8 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
|
|
|
158
164
|
RegisteredRoutes,
|
|
159
165
|
EscapedTemplates,
|
|
160
166
|
ClearingAnalysis,
|
|
167
|
+
TemplateCallsAnalysis,
|
|
168
|
+
SignaturesAnalysis,
|
|
161
169
|
)
|
|
162
170
|
|
|
163
171
|
|
|
@@ -198,6 +206,8 @@ class ResultsEvicted(TransformationPass):
|
|
|
198
206
|
RegisteredRoutes,
|
|
199
207
|
EscapedTemplates,
|
|
200
208
|
ClearingAnalysis,
|
|
209
|
+
TemplateCallsAnalysis,
|
|
210
|
+
SignaturesAnalysis,
|
|
201
211
|
}
|
|
202
212
|
)
|
|
203
213
|
|
|
@@ -261,6 +271,7 @@ def build_manager(
|
|
|
261
271
|
table = (models or SecurityModelRegistry()).freeze()
|
|
262
272
|
manager.provide(SecurityModelAnalysis, table)
|
|
263
273
|
manager.provide(MembersAnalysis, table.members_by_class())
|
|
274
|
+
manager.provide(SignaturesAnalysis, table.signatures())
|
|
264
275
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
265
276
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
266
277
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -316,6 +327,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
|
|
|
316
327
|
table = plugin_models(loaded.plugin for loaded in registry)
|
|
317
328
|
manager.provide(SecurityModelAnalysis, table)
|
|
318
329
|
manager.provide(MembersAnalysis, table.members_by_class())
|
|
330
|
+
manager.provide(SignaturesAnalysis, table.signatures())
|
|
319
331
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
320
332
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
321
333
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -411,6 +423,7 @@ def analyze_project(
|
|
|
411
423
|
for manager in managers.values():
|
|
412
424
|
manager.provide(SecurityModelAnalysis, models)
|
|
413
425
|
manager.provide(MembersAnalysis, members)
|
|
426
|
+
manager.provide(SignaturesAnalysis, models.signatures())
|
|
414
427
|
manager.provide(DependencyAnalysis, dependencies)
|
|
415
428
|
|
|
416
429
|
imports: dict[str, ImportTable] = {}
|
|
@@ -431,13 +444,20 @@ def analyze_project(
|
|
|
431
444
|
for symbol, registered in _routes_of(analysable[name]).items():
|
|
432
445
|
routes.setdefault(symbol, registered)
|
|
433
446
|
escaped = escaped_templates(root)
|
|
447
|
+
templates = project_templates(root)
|
|
434
448
|
clearing = models.clearing(escaped)
|
|
449
|
+
template_calls = models.template_calls(
|
|
450
|
+
templates.filters, request_processor(modules[name] for name in sorted(analysable))
|
|
451
|
+
)
|
|
435
452
|
for manager in analysable.values():
|
|
436
453
|
manager.provide(RegisteredRoutes, routes)
|
|
437
454
|
manager.provide(EscapedTemplates, escaped)
|
|
438
455
|
manager.provide(ClearingAnalysis, clearing)
|
|
456
|
+
manager.provide(TemplateCallsAnalysis, template_calls)
|
|
439
457
|
|
|
440
|
-
configuration = _configuration_key(
|
|
458
|
+
configuration = _configuration_key(
|
|
459
|
+
components, plugins, models, advisories, dependencies, routes, escaped, template_calls
|
|
460
|
+
)
|
|
441
461
|
keys = module_keys(
|
|
442
462
|
graph,
|
|
443
463
|
{name: fingerprint(configuration, str(files[name].source_id), name, files[name].text) for name in analysable},
|
|
@@ -479,6 +499,7 @@ def analyze_project(
|
|
|
479
499
|
advisory_paths,
|
|
480
500
|
_encode_routes(routes),
|
|
481
501
|
tuple(sorted(escaped)),
|
|
502
|
+
template_calls,
|
|
482
503
|
),
|
|
483
504
|
)
|
|
484
505
|
for component in pending
|
|
@@ -516,7 +537,9 @@ def analyze_project(
|
|
|
516
537
|
frozenset(),
|
|
517
538
|
reads={f: tuple(r) for f, r in reads.items()},
|
|
518
539
|
)
|
|
519
|
-
context = ProjectContext(
|
|
540
|
+
context = ProjectContext(
|
|
541
|
+
graph, dependencies, advisories, analysable, call_graphs, policy, root, functions, templates
|
|
542
|
+
)
|
|
520
543
|
for plugin in all_plugins:
|
|
521
544
|
if isinstance(plugin, ProjectPlugin):
|
|
522
545
|
findings.extend(plugin.analyze_project(context))
|
|
@@ -526,6 +549,9 @@ def analyze_project(
|
|
|
526
549
|
findings = [_sourced(finding, origins) for finding in findings]
|
|
527
550
|
accepted = tuple(f for f in findings if policy.accepts(f))
|
|
528
551
|
kept, suppressed = partition(apply_policy(policy, findings), _text_of(sources))
|
|
552
|
+
unread = list(templates.unread)
|
|
553
|
+
for name in sorted(call_graphs):
|
|
554
|
+
unread.extend(unread_renders(name, call_graphs[name], models, templates.names))
|
|
529
555
|
return ProjectAnalysis(
|
|
530
556
|
graph,
|
|
531
557
|
index,
|
|
@@ -534,7 +560,7 @@ def analyze_project(
|
|
|
534
560
|
MappingProxyType(keys),
|
|
535
561
|
reused,
|
|
536
562
|
advisories,
|
|
537
|
-
Coverage(tuple(sorted(coverage, key=lambda c: c.path))),
|
|
563
|
+
Coverage(tuple(sorted(coverage, key=lambda c: c.path)), tuple(unread)),
|
|
538
564
|
suppressed,
|
|
539
565
|
accepted,
|
|
540
566
|
components,
|
|
@@ -686,6 +712,7 @@ class _Batch:
|
|
|
686
712
|
advisory_paths: tuple[Path, ...] = ()
|
|
687
713
|
routes: tuple[tuple[str, str, str, int], ...] = ()
|
|
688
714
|
escaped: tuple[str, ...] = ()
|
|
715
|
+
template_calls: TemplateCalls = field(default_factory=TemplateCalls)
|
|
689
716
|
|
|
690
717
|
|
|
691
718
|
def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
@@ -709,10 +736,12 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
|
709
736
|
for manager in managers.values():
|
|
710
737
|
manager.provide(SecurityModelAnalysis, models)
|
|
711
738
|
manager.provide(MembersAnalysis, members)
|
|
739
|
+
manager.provide(SignaturesAnalysis, models.signatures())
|
|
712
740
|
manager.provide(DependencyAnalysis, dependencies)
|
|
713
741
|
manager.provide(RegisteredRoutes, routes)
|
|
714
742
|
manager.provide(EscapedTemplates, escaped)
|
|
715
743
|
manager.provide(ClearingAnalysis, models.clearing(escaped))
|
|
744
|
+
manager.provide(TemplateCallsAnalysis, batch.template_calls)
|
|
716
745
|
module_plugins = tuple(p for p in all_plugins if not isinstance(p, ProjectPlugin))
|
|
717
746
|
results = _analyse_managers(managers, decode_index(batch.seed), module_plugins, affected)
|
|
718
747
|
return {name: encode(entry) for name, entry in results.items()}
|
|
@@ -741,6 +770,7 @@ def _configuration_key(
|
|
|
741
770
|
dependencies: DependencyGraph,
|
|
742
771
|
routes: Routes | None = None,
|
|
743
772
|
escaped: frozenset[str] = frozenset(),
|
|
773
|
+
template_calls: TemplateCalls | None = None,
|
|
744
774
|
) -> str:
|
|
745
775
|
"""Everything a module's results depend on besides the project sources (§11)."""
|
|
746
776
|
|
|
@@ -758,6 +788,7 @@ def _configuration_key(
|
|
|
758
788
|
repr(dependencies.errors),
|
|
759
789
|
repr(_encode_routes(routes or {})),
|
|
760
790
|
repr(sorted(escaped)),
|
|
791
|
+
repr(template_calls),
|
|
761
792
|
)
|
|
762
793
|
|
|
763
794
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
"No findings" only means something when the reader knows what was analysed. Each file
|
|
4
4
|
is ``analysed``, a ``syntax-error`` (the frontend rejected it) or ``unreadable`` (it could
|
|
5
|
-
not be decoded); analysed files count their functions and how many lowered.
|
|
5
|
+
not be decoded); analysed files count their functions and how many lowered. The
|
|
6
|
+
templates the project names but the run could not read are listed, each where the
|
|
7
|
+
project names it: they may call any template filter.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
from __future__ import annotations
|
|
@@ -21,6 +23,7 @@ class FileCoverage:
|
|
|
21
23
|
@dataclass(frozen=True)
|
|
22
24
|
class Coverage:
|
|
23
25
|
details: tuple[FileCoverage, ...] = ()
|
|
26
|
+
unread_templates: tuple[str, ...] = ()
|
|
24
27
|
|
|
25
28
|
@property
|
|
26
29
|
def files(self) -> int:
|
|
@@ -8,6 +8,8 @@ from coretrace_python.interprocedural.callgraph import (
|
|
|
8
8
|
ExternalSymbol,
|
|
9
9
|
KnownFunction,
|
|
10
10
|
ModuleFunction,
|
|
11
|
+
Signature,
|
|
12
|
+
SignaturesAnalysis,
|
|
11
13
|
SymbolRead,
|
|
12
14
|
Target,
|
|
13
15
|
UnknownTarget,
|
|
@@ -20,6 +22,7 @@ from coretrace_python.interprocedural.modulegraph import (
|
|
|
20
22
|
project_symbol,
|
|
21
23
|
)
|
|
22
24
|
from coretrace_python.interprocedural.summaries import (
|
|
25
|
+
FILTER_ARGUMENTS,
|
|
23
26
|
Cleared,
|
|
24
27
|
Clearing,
|
|
25
28
|
ClearingAnalysis,
|
|
@@ -31,10 +34,14 @@ from coretrace_python.interprocedural.summaries import (
|
|
|
31
34
|
SummaryAnalysis,
|
|
32
35
|
SummaryIndex,
|
|
33
36
|
SummaryTable,
|
|
37
|
+
TemplateCalls,
|
|
38
|
+
TemplateCallsAnalysis,
|
|
39
|
+
TemplateFilter,
|
|
34
40
|
cleared_by,
|
|
35
41
|
)
|
|
36
42
|
|
|
37
43
|
__all__ = [
|
|
44
|
+
"FILTER_ARGUMENTS",
|
|
38
45
|
"Arguments",
|
|
39
46
|
"CallGraph",
|
|
40
47
|
"CallGraphAnalysis",
|
|
@@ -51,11 +58,16 @@ __all__ = [
|
|
|
51
58
|
"Mutation",
|
|
52
59
|
"NonlocalWrite",
|
|
53
60
|
"ProjectSummaries",
|
|
61
|
+
"Signature",
|
|
62
|
+
"SignaturesAnalysis",
|
|
54
63
|
"SummaryAnalysis",
|
|
55
64
|
"SummaryIndex",
|
|
56
65
|
"SummaryTable",
|
|
57
66
|
"SymbolRead",
|
|
58
67
|
"Target",
|
|
68
|
+
"TemplateCalls",
|
|
69
|
+
"TemplateCallsAnalysis",
|
|
70
|
+
"TemplateFilter",
|
|
59
71
|
"UnknownTarget",
|
|
60
72
|
"build_module_graph",
|
|
61
73
|
"cleared_by",
|
|
@@ -9,6 +9,7 @@ reads, called or not: reading ``request.form`` runs its getter.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import re
|
|
12
13
|
from collections.abc import Mapping
|
|
13
14
|
from dataclasses import dataclass, field
|
|
14
15
|
from types import MappingProxyType
|
|
@@ -104,6 +105,30 @@ class SymbolRead:
|
|
|
104
105
|
symbol: SymbolId
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class Signature:
|
|
110
|
+
"""What the parameters of the methods of a class denote when the canonical symbol
|
|
111
|
+
of its base matches ``pattern``, by position after ``self``: a class symbol, or None
|
|
112
|
+
for a parameter denoting none. A method taking another number of parameters has
|
|
113
|
+
none. The engine derives signatures from the security models (§16)."""
|
|
114
|
+
|
|
115
|
+
pattern: str
|
|
116
|
+
parameters: tuple[SymbolId | None, ...]
|
|
117
|
+
|
|
118
|
+
def matches(self, base: SymbolId) -> bool:
|
|
119
|
+
return re.search(self.pattern, base.canonical_name) is not None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SignaturesAnalysis(Analysis[tuple[Signature, ...]]):
|
|
123
|
+
"""The signatures the models declare, provided by the engine; none on its own."""
|
|
124
|
+
|
|
125
|
+
name: ClassVar[str] = "interprocedural.signatures"
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def compute(cls, ctx: AnalysisContext) -> tuple[Signature, ...]:
|
|
129
|
+
return ()
|
|
130
|
+
|
|
131
|
+
|
|
107
132
|
@dataclass(frozen=True)
|
|
108
133
|
class ModuleFunction:
|
|
109
134
|
"""One function of a module as a project plugin sees it: the name the call graph
|
|
@@ -357,6 +382,20 @@ def _annotated(
|
|
|
357
382
|
return found
|
|
358
383
|
|
|
359
384
|
|
|
385
|
+
def _declared(
|
|
386
|
+
function: nodes.Function, ssa: FunctionIR, bases: tuple[SymbolId, ...], signatures: tuple[Signature, ...]
|
|
387
|
+
) -> dict[Value, SymbolId]:
|
|
388
|
+
"""Parameters a signature types by their position, for a method of a class one of
|
|
389
|
+
whose ``bases`` matches the signature and which takes the parameters it lists."""
|
|
390
|
+
|
|
391
|
+
given = function.parameters[1:]
|
|
392
|
+
for signature in signatures:
|
|
393
|
+
if any(signature.matches(base) for base in bases) and len(given) == len(signature.parameters):
|
|
394
|
+
typed = zip(ssa.parameters[1:], signature.parameters, strict=False)
|
|
395
|
+
return {value: symbol for value, symbol in typed if symbol is not None}
|
|
396
|
+
return {}
|
|
397
|
+
|
|
398
|
+
|
|
360
399
|
def _typed_with_module_classes(
|
|
361
400
|
function: nodes.Function, ssa: FunctionIR, classes: Mapping[str, frozenset[str]]
|
|
362
401
|
) -> dict[Value, str]:
|
|
@@ -372,12 +411,15 @@ def _typed_with_module_classes(
|
|
|
372
411
|
|
|
373
412
|
class CallGraphAnalysis(Analysis[CallGraph]):
|
|
374
413
|
name: ClassVar[str] = "interprocedural.callgraph"
|
|
375
|
-
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
|
|
414
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
|
|
415
|
+
{SSAAnalysis, ScopeAnalysis, SymbolAnalysis, SignaturesAnalysis}
|
|
416
|
+
)
|
|
376
417
|
|
|
377
418
|
@classmethod
|
|
378
419
|
def compute(cls, ctx: AnalysisContext) -> CallGraph:
|
|
379
420
|
scopes = ctx.get(ScopeAnalysis)
|
|
380
421
|
table = ctx.get(SymbolAnalysis)
|
|
422
|
+
signatures = ctx.get(SignaturesAnalysis)
|
|
381
423
|
definitions: dict[str, nodes.Function] = {}
|
|
382
424
|
for function in analyzable_functions(ctx.module):
|
|
383
425
|
# A property and its setter, or a redefinition, share a qualified name; each
|
|
@@ -396,14 +438,15 @@ class CallGraphAnalysis(Analysis[CallGraph]):
|
|
|
396
438
|
for statement in ctx.module.body
|
|
397
439
|
if isinstance(statement, nodes.Class)
|
|
398
440
|
}
|
|
441
|
+
# Inheritance follows the first base that resolves; a signature may match any.
|
|
399
442
|
bases: dict[str, SymbolId] = {}
|
|
443
|
+
ancestry: dict[str, tuple[SymbolId, ...]] = {}
|
|
400
444
|
for statement in ctx.module.body:
|
|
401
445
|
if isinstance(statement, nodes.Class):
|
|
402
|
-
for
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
break
|
|
446
|
+
resolved = [table.resolve_expression(scopes.module_scope.id, base) for base in statement.bases]
|
|
447
|
+
ancestry[statement.name] = tuple(symbol for symbol in resolved if symbol is not None)
|
|
448
|
+
if ancestry[statement.name]:
|
|
449
|
+
bases[statement.name] = ancestry[statement.name][0]
|
|
407
450
|
sites: dict[str, tuple[CallSite, ...]] = {}
|
|
408
451
|
symbols: dict[str, Mapping[Value, SymbolId]] = {}
|
|
409
452
|
reads: dict[str, tuple[SymbolRead, ...]] = {}
|
|
@@ -418,11 +461,13 @@ class CallGraphAnalysis(Analysis[CallGraph]):
|
|
|
418
461
|
owner = name.rsplit(".", 1)[0] if "." in name and name.rsplit(".", 1)[0] in classes else None
|
|
419
462
|
if owner is not None and _is_staticmethod(function):
|
|
420
463
|
owner = None # a static method has no receiver
|
|
464
|
+
# A signature types parameters by their position; an annotation says more.
|
|
465
|
+
declared = _declared(function, ssa, ancestry.get(owner, ()) if owner is not None else (), signatures)
|
|
421
466
|
targets, symbols[name] = resolve_targets(
|
|
422
467
|
ssa,
|
|
423
468
|
scopes,
|
|
424
469
|
known,
|
|
425
|
-
_annotated(function, ssa, scopes, table),
|
|
470
|
+
{**declared, **_annotated(function, ssa, scopes, table)},
|
|
426
471
|
frozenset(definitions),
|
|
427
472
|
classes,
|
|
428
473
|
owner,
|