coretrace-python-analyzer 0.13.0__tar.gz → 0.15.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.13.0 → coretrace_python_analyzer-0.15.0}/CHANGELOG.md +20 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/docs/plugins.md +8 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/docs/usage.md +22 -5
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/__init__.py +1 -1
- coretrace_python_analyzer-0.15.0/src/coretrace_python/abstract/strings.py +98 -0
- coretrace_python_analyzer-0.15.0/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +47 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +1 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +1 -1
- coretrace_python_analyzer-0.15.0/src/coretrace_python/bundled/security/ssrf/ssrf.py +49 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/advisories.py +6 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/correlation.py +36 -5
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/graph.py +4 -2
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/engine.py +9 -6
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/detectors.py +13 -2
- coretrace_python_analyzer-0.15.0/src/coretrace_python/taint/urls.py +70 -0
- coretrace_python_analyzer-0.15.0/tests/test_advisory_url_host.py +145 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_detectors.py +4 -1
- coretrace_python_analyzer-0.15.0/tests/test_open_redirect_destinations.py +116 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.15.0/tests/test_ssrf_destinations.py +154 -0
- coretrace_python_analyzer-0.15.0/tests/test_url_proof.py +80 -0
- coretrace_python_analyzer-0.13.0/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -17
- coretrace_python_analyzer-0.13.0/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -17
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/README.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/provenance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/taint/engine.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/taint/models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_attacker_arguments.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_code_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_members.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_provenance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Precision
|
|
6
|
+
|
|
7
|
+
- `open-redirect` judges a flow on the destination a browser resolves from the target's constant text, with its own criteria rather than SSRF's. A reference on the current site keeps the browser there, so the flow is refuted. Such a reference is `/` followed by a character that is not a slash, a backslash, a space or a control character, as in `"/profile/" + id`; or `?`, `#`, or a relative path whose first segment holds no `:`. A fixed absolute host makes the flow a hotspot, since the input chooses the path, where that host may redirect again. Everything else stays a vulnerability, including `"/" + next`: `//evil.com` is another host. The URL facts both rules read are one shared proof, `coretrace_python.taint.urls`, and `ssrf` now reads it too, with the same verdicts. The regression corpus is unchanged: its three `open-redirect` findings redirect to values built without constant text (#168).
|
|
8
|
+
|
|
9
|
+
### Advisories
|
|
10
|
+
|
|
11
|
+
- An advisory entry point may declare a `host` condition: the attacker must choose the host of the URL passed as the named argument. It is decided from the URL's constant text, the proof `ssrf` and `open-redirect` read. A fixed host with redirects disabled in the call leaves the call reachable, not exploitable. A fixed host with redirects followed keeps it exploitable, because a redirect may lead to a host the attacker controls, and the condition pending review says so. Input in the query does not choose the host. Anything else stays pending review, and so do the environment conditions. A `host` condition must name its argument and carry nothing else (#170).
|
|
12
|
+
|
|
13
|
+
## 0.14.0 (2026-09-26)
|
|
14
|
+
|
|
15
|
+
### Precision
|
|
16
|
+
|
|
17
|
+
- `ssrf` judges a flow on how `+` and f-strings build its URL. A fixed host is a limited proof: when constant text fixes `scheme://host` and ends the authority with `/`, `?` or `#` before any input, as in `"https://api.example.com/users/" + id`, the flow is a hotspot, because the input still chooses the path and a redirect may lead elsewhere. It is refuted only when the constant text fixes the path too, the input reaching the query or fragment only, and the call disables redirects (`allow_redirects=False`, `follow_redirects=False`). A module-level name counts as known text when the module binds it once to a string literal and never rebinds it. Everything else stays a vulnerability, including a base of unknown value such as an imported `BASE_URL`, which may leave the authority open. The advisory correlation keeps its own verdict. The regression corpus is unchanged: its six `ssrf` findings build their URL from values of unknown content (#165).
|
|
18
|
+
|
|
19
|
+
### Plugins
|
|
20
|
+
|
|
21
|
+
- `TaintDetector.judge(ctx, function, flow, verdict)` lets a rule refine the refutation's verdict on a flow for its own findings, without changing the verdict other consumers of the flow read (#165).
|
|
22
|
+
|
|
3
23
|
## 0.13.0 (2026-09-26)
|
|
4
24
|
|
|
5
25
|
### Precision
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.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
|
|
@@ -115,6 +115,14 @@ ordinary input reaching a database write is not a plaintext credential; `LOG`, f
|
|
|
115
115
|
values that must not be written to a log as they are; and `PII`, for personal data
|
|
116
116
|
that must not leave the application through a log, a third party or plain storage.
|
|
117
117
|
|
|
118
|
+
A rule may know more about its own sinks than the refutation does: `judge(ctx, function,
|
|
119
|
+
flow, verdict)` returns the rule's verdict on a flow, given the refutation's, and by
|
|
120
|
+
default returns it unchanged. `ssrf` and `open-redirect` read there what the URL's
|
|
121
|
+
constant text proves, a proof they share (`coretrace_python.taint.urls`) and each
|
|
122
|
+
judges by its own criteria. The refutation's
|
|
123
|
+
verdict, which other consumers of the flow such as the advisory correlation read, does
|
|
124
|
+
not change; a rule reading more analyses declares them in its `requires`.
|
|
125
|
+
|
|
118
126
|
### Security models: `ModelPlugin`
|
|
119
127
|
|
|
120
128
|
Declares sources, sinks and sanitizers for a library or a framework. A model plugin
|
|
@@ -114,10 +114,10 @@ variable is not treated that way yet.
|
|
|
114
114
|
| `sql-injection` | A database statement (`cursor.execute`, SQLAlchemy `text`, Django `raw`, …). Parameters of a parameterised query are not statements. |
|
|
115
115
|
| `nosql-injection` | A NoSQL query, such as a MongoDB filter, given attacker input that may hold a structure: a JSON body giving `{"$ne": null}` matches any value. The input that may hold a structure is JSON, the raw bodies a `json.loads` decodes, files, WebSocket messages and standard input. Text cannot hold an operator: form fields, query parameters, headers, cookies, URL parameters, the command line, the environment, and an entry-point parameter annotated with scalars (`str`, `int`, `Optional[str]`, `Annotated[str, Query()]`, `list[str]`, …), which FastAPI validates. A Django, REST framework, aiohttp or tornado view reads a request object: its text attributes (`GET`, `POST`, `headers`, `query_params`, `query`, `match_info`, `arguments`, …) and the URL parameters after it are text, its body, files and `data` are not. Limits: a helper function the request is passed to reads it as a whole; the keys of a dict are not followed, so a string concatenated into `$where` is not reported. The analyzer ships no NoSQL sink: model plugins declare them. |
|
|
116
116
|
| `path-traversal` | A file system path (`open`, `send_file`, `os.remove`, …). |
|
|
117
|
-
| `ssrf` | The URL of an HTTP client request (Requests, httpx, `urllib`), by position or as `url=`; the body, headers and query parameters are not the destination. |
|
|
117
|
+
| `ssrf` | The URL of an HTTP client request (Requests, httpx, `urllib`), by position or as `url=`; the body, headers and query parameters are not the destination. How `+` and f-strings build the URL can prove part of its destination. When constant text fixes `scheme://host` and ends the authority with `/`, `?` or `#` before any input (`"https://api.example.com/users/" + id`, or a module-level string bound once and never rebound), the flow is a hotspot: the input still chooses the path, which may reach a sensitive resource, and a redirect may lead elsewhere. With the path fixed too, the input reaching the query or fragment only, and the call disabling redirects (`allow_redirects=False`, `follow_redirects=False`), the destination is fixed and the flow is refuted. Anything unproven stays a vulnerability: a base of unknown value (imported, a call result), a constant leaving the authority open (`"https:/"`, no `/` after the host, a variable port), `urljoin`, `%`, `format`, a URL built in a helper function. |
|
|
118
118
|
| `xss` | An HTTP response body built without escaping. A Django template rendered by `render_to_string` escapes, where the analyzer can establish it (below). |
|
|
119
119
|
| `insecure-deserialization` | `pickle`, `marshal`, `dill`, `jsonpickle`, `shelve`, and the YAML loaders (`yaml.load`, `load_all`, `full_load`, `unsafe_load` and their several-document versions); `yaml.load` and `load_all` given a safe loader (`SafeLoader`, `BaseLoader`) are not. |
|
|
120
|
-
| `open-redirect` | An HTTP redirect target. |
|
|
120
|
+
| `open-redirect` | An HTTP redirect target, judged on the destination a browser resolves from the target's constant text, as `+` and f-strings build it. A reference on the current site keeps the browser there, where any further redirect is the project's own code, analysed on its own, so the flow is refuted. Such a reference is `/` followed by a character that is not a slash, a backslash, a space or a control character (`"/profile/" + id`), `?`, `#`, or a relative path whose first segment ends in the text without a `:`. A fixed absolute host (`"https://example.com/" + path`) sends the browser there, but the input chooses the path, where that host may redirect again: a hotspot. Anything else stays a vulnerability. That covers `"/" + next`, since `//evil.com` is another host, a host left open, a base of unknown value and a target built in a helper function. |
|
|
121
121
|
| `plaintext-credential-storage` | A parameter named like a password stored in a database without hashing. Medium confidence, since a name is a hint. |
|
|
122
122
|
| `exploitable-vulnerability` | An API affected by an advisory of a vulnerable requirement, see Dependencies. |
|
|
123
123
|
|
|
@@ -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.15.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.15.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:…"}
|
|
@@ -386,6 +386,23 @@ a call passing a variable, or unpacking `*args` or `**kwargs`, leaves the condit
|
|
|
386
386
|
pending review. A `semantic` condition — the document's tags, the platform, the
|
|
387
387
|
template's origin — cannot be decided and is always pending review, never dropped.
|
|
388
388
|
|
|
389
|
+
A `host` condition says the attacker must choose the host of the URL passed as
|
|
390
|
+
`argument` (or at `position`), as credentials leaking to the host of a crafted URL
|
|
391
|
+
need: `{"kind": "host", "text": "…", "argument": "url", "position": 0}`. It is
|
|
392
|
+
decided from the URL's constant text, as `ssrf` and `open-redirect` read it, when the
|
|
393
|
+
attacker's input is passed in that argument. The outcome has three cases:
|
|
394
|
+
|
|
395
|
+
- Constant text fixes the host (`"https://api.example.com/" + path`) and the call
|
|
396
|
+
disables redirects (`allow_redirects=False`, `follow_redirects=False`): the call is
|
|
397
|
+
reachable, not exploitable.
|
|
398
|
+
- Constant text fixes the host but redirects may be followed: the call stays
|
|
399
|
+
exploitable. The fixed initial host does not clear the chain, since a redirect may
|
|
400
|
+
lead to a host the attacker controls, and the condition pending review says so.
|
|
401
|
+
Input in the query does not choose the host.
|
|
402
|
+
- Otherwise the condition stays pending review.
|
|
403
|
+
|
|
404
|
+
The other conditions, the environment included, stay pending as before.
|
|
405
|
+
|
|
389
406
|
Every dependency finding records the highest level of evidence established in its
|
|
390
407
|
`level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
|
|
391
408
|
(a module of the package is imported somewhere), `reachable` (an entry point or affected
|
|
@@ -436,7 +453,7 @@ project's behalf would go unseen. Without a lock file, a package is never
|
|
|
436
453
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
437
454
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
438
455
|
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
439
|
-
(`coretrace-python-analyzer 0.
|
|
456
|
+
(`coretrace-python-analyzer 0.15.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
440
457
|
`@id` changes with the advisory data a statement was decided with.
|
|
441
458
|
|
|
442
459
|
## Large projects
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""The constant text a string starts with (architecture §18): what ``+`` and f-strings
|
|
2
|
+
build from string literals and from module-level names bound once to one. Anything
|
|
3
|
+
else, a parameter, a call result, an import, ``%`` or ``format``, is text of unknown
|
|
4
|
+
value."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from types import MappingProxyType
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
13
|
+
from coretrace_python.cfg import CFGError
|
|
14
|
+
from coretrace_python.hir import nodes
|
|
15
|
+
from coretrace_python.ir.lowering import (
|
|
16
|
+
MODULE_BODY,
|
|
17
|
+
LoweringError,
|
|
18
|
+
PyIRAnalysis,
|
|
19
|
+
analyzable_functions,
|
|
20
|
+
)
|
|
21
|
+
from coretrace_python.ir.model import (
|
|
22
|
+
BinaryOp,
|
|
23
|
+
BuildString,
|
|
24
|
+
Constant,
|
|
25
|
+
ForNext,
|
|
26
|
+
Global,
|
|
27
|
+
Instruction,
|
|
28
|
+
SetGlobal,
|
|
29
|
+
StoreLocal,
|
|
30
|
+
Value,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def leading_text(value: Value, defs: Mapping[Value, Instruction], strings: Mapping[str, str]) -> tuple[str, bool]:
|
|
35
|
+
"""The constant text ``value`` starts with, and whether that text is all of it, given
|
|
36
|
+
the definitions of the function's values and the module's ``strings``."""
|
|
37
|
+
|
|
38
|
+
made = defs.get(value)
|
|
39
|
+
if isinstance(made, Constant):
|
|
40
|
+
return (made.value, True) if isinstance(made.value, str) else ("", False)
|
|
41
|
+
if isinstance(made, Global):
|
|
42
|
+
known = strings.get(made.name)
|
|
43
|
+
return ("", False) if known is None else (known, True)
|
|
44
|
+
if isinstance(made, BinaryOp) and made.operator == "add":
|
|
45
|
+
left, whole = leading_text(made.left, defs, strings)
|
|
46
|
+
if not whole:
|
|
47
|
+
return left, False
|
|
48
|
+
right, whole = leading_text(made.right, defs, strings)
|
|
49
|
+
return left + right, whole
|
|
50
|
+
if isinstance(made, BuildString):
|
|
51
|
+
text = ""
|
|
52
|
+
for part in made.parts:
|
|
53
|
+
piece, whole = leading_text(part, defs, strings)
|
|
54
|
+
text += piece
|
|
55
|
+
if not whole:
|
|
56
|
+
return text, False
|
|
57
|
+
return text, True
|
|
58
|
+
return "", False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ModuleStringsAnalysis(Analysis[Mapping[str, str]]):
|
|
62
|
+
"""The module-level names bound once, to a string literal, and never rebound: by no
|
|
63
|
+
other statement of the module body, no function assigning them as ``global``, no
|
|
64
|
+
definition of that name. A module that does not lower entirely has none."""
|
|
65
|
+
|
|
66
|
+
name: ClassVar[str] = "abstract.module_strings"
|
|
67
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({PyIRAnalysis})
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def compute(cls, ctx: AnalysisContext) -> Mapping[str, str]:
|
|
71
|
+
try:
|
|
72
|
+
functions = [ctx.get(PyIRAnalysis, function) for function in analyzable_functions(ctx.module)]
|
|
73
|
+
except (LoweringError, CFGError):
|
|
74
|
+
return MappingProxyType({})
|
|
75
|
+
body = next((function for function in functions if function.name == MODULE_BODY), None)
|
|
76
|
+
if body is None:
|
|
77
|
+
return MappingProxyType({})
|
|
78
|
+
defs = {i.result: i for block in body.blocks for i in block.instructions if i.result is not None}
|
|
79
|
+
stored: dict[str, list[Value | None]] = {}
|
|
80
|
+
for block in body.blocks:
|
|
81
|
+
for instruction in block.instructions:
|
|
82
|
+
if isinstance(instruction, StoreLocal):
|
|
83
|
+
stored.setdefault(instruction.name, []).append(instruction.value)
|
|
84
|
+
if isinstance(block.terminator, ForNext):
|
|
85
|
+
# A ``for`` binds its target to each item.
|
|
86
|
+
stored.setdefault(block.terminator.target, []).append(None)
|
|
87
|
+
rebound = {
|
|
88
|
+
i.name for function in functions for block in function.blocks for i in block.instructions
|
|
89
|
+
if isinstance(i, SetGlobal)
|
|
90
|
+
}
|
|
91
|
+
rebound |= {s.name for s in ctx.module.body if isinstance(s, nodes.Function | nodes.Class)}
|
|
92
|
+
found: dict[str, str] = {}
|
|
93
|
+
for name, values in stored.items():
|
|
94
|
+
value = values[0] if len(values) == 1 and name not in rebound else None
|
|
95
|
+
made = defs.get(value) if value is not None else None
|
|
96
|
+
if isinstance(made, Constant) and isinstance(made.value, str):
|
|
97
|
+
found[name] = made.value
|
|
98
|
+
return MappingProxyType(found)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Open redirect: attacker-controlled input reaching a open redirect sink.
|
|
2
|
+
|
|
3
|
+
The rule draws its verdict from what the target's constant text proves (``taint.urls``),
|
|
4
|
+
as a browser resolves it. A reference on the current site (``/profile/``, ``?page=``,
|
|
5
|
+
``profile/``) keeps the browser on the site, where any further redirect is the project's
|
|
6
|
+
own code, analysed on its own: refuted. A fixed absolute host sends the browser there,
|
|
7
|
+
but the input chooses the path, where that host may redirect again: a hotspot. Anything
|
|
8
|
+
unproven, ``"/" + next`` included, stays a vulnerability.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import ClassVar
|
|
14
|
+
|
|
15
|
+
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
16
|
+
from coretrace_python.analysis import AnyAnalysis
|
|
17
|
+
from coretrace_python.findings import Severity
|
|
18
|
+
from coretrace_python.findings.refutation import Status, Verdict
|
|
19
|
+
from coretrace_python.hir import nodes
|
|
20
|
+
from coretrace_python.ir.ssa import SSAAnalysis
|
|
21
|
+
from coretrace_python.plugins import PluginContext, TaintDetector
|
|
22
|
+
from coretrace_python.taint import TaintFlow, TaintKind
|
|
23
|
+
from coretrace_python.taint.urls import flow_url
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpenRedirectPlugin(TaintDetector):
|
|
27
|
+
name: ClassVar[str] = "open-redirect"
|
|
28
|
+
rule_id: ClassVar[str] = "open-redirect"
|
|
29
|
+
kind: ClassVar[TaintKind] = TaintKind.REDIRECT
|
|
30
|
+
severity: ClassVar[Severity] = Severity.MEDIUM
|
|
31
|
+
title: ClassVar[str] = "Open redirect"
|
|
32
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = TaintDetector.requires | {SSAAnalysis, ModuleStringsAnalysis}
|
|
33
|
+
|
|
34
|
+
def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
|
|
35
|
+
ssa = ctx.get(SSAAnalysis, function)
|
|
36
|
+
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
37
|
+
target = flow_url(flow, defs, ctx.get(ModuleStringsAnalysis))
|
|
38
|
+
if target.relative:
|
|
39
|
+
return Verdict(flow, Status.REFUTED, f"the browser stays on the site: the target starts with {target.text!r}")
|
|
40
|
+
if target.origin is not None and verdict.status is Status.VULNERABILITY:
|
|
41
|
+
return Verdict(
|
|
42
|
+
flow,
|
|
43
|
+
Status.HOTSPOT,
|
|
44
|
+
f"the browser goes to the host fixed by {target.origin!r}, but the input chooses the path, "
|
|
45
|
+
"where that host may redirect again",
|
|
46
|
+
)
|
|
47
|
+
return verdict
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name = "open-redirect"
|
|
2
2
|
version = "1.0.0"
|
|
3
3
|
plugin_api = ">=1,<2"
|
|
4
|
-
requires = ["taint.flows", "findings.refutation"]
|
|
4
|
+
requires = ["taint.flows", "findings.refutation", "ir.ssa", "abstract.module_strings"]
|
|
5
5
|
provides = ["vulnerability.open-redirect"]
|
|
6
6
|
|
|
7
7
|
[entrypoint]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Server-side request forgery: attacker-controlled input reaching a SSRF sink.
|
|
2
|
+
|
|
3
|
+
The rule draws its verdict from what the URL's constant text proves (``taint.urls``).
|
|
4
|
+
A fixed host keeps the input from choosing it, but the input still chooses the path,
|
|
5
|
+
which may reach a sensitive resource of that host, and a redirect may lead elsewhere: a
|
|
6
|
+
hotspot. With the path fixed too, the input reaching the query or fragment only, and
|
|
7
|
+
redirects disabled in the call, the destination is fixed: refuted. Anything unproven,
|
|
8
|
+
a base of unknown value or a URL built in a helper function, stays a vulnerability.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import ClassVar
|
|
14
|
+
|
|
15
|
+
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
16
|
+
from coretrace_python.analysis import AnyAnalysis
|
|
17
|
+
from coretrace_python.findings import Severity
|
|
18
|
+
from coretrace_python.findings.refutation import Status, Verdict
|
|
19
|
+
from coretrace_python.hir import nodes
|
|
20
|
+
from coretrace_python.ir.ssa import SSAAnalysis
|
|
21
|
+
from coretrace_python.plugins import PluginContext, TaintDetector
|
|
22
|
+
from coretrace_python.taint import TaintFlow, TaintKind
|
|
23
|
+
from coretrace_python.taint.urls import flow_url, redirects_disabled
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SsrfPlugin(TaintDetector):
|
|
27
|
+
name: ClassVar[str] = "ssrf"
|
|
28
|
+
rule_id: ClassVar[str] = "ssrf"
|
|
29
|
+
kind: ClassVar[TaintKind] = TaintKind.SSRF
|
|
30
|
+
severity: ClassVar[Severity] = Severity.HIGH
|
|
31
|
+
title: ClassVar[str] = "Server-side request forgery"
|
|
32
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = TaintDetector.requires | {SSAAnalysis, ModuleStringsAnalysis}
|
|
33
|
+
|
|
34
|
+
def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
|
|
35
|
+
ssa = ctx.get(SSAAnalysis, function)
|
|
36
|
+
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
37
|
+
url = flow_url(flow, defs, ctx.get(ModuleStringsAnalysis))
|
|
38
|
+
if url.origin is None:
|
|
39
|
+
return verdict
|
|
40
|
+
no_redirects = redirects_disabled(flow.sink_arguments)
|
|
41
|
+
if url.path_fixed:
|
|
42
|
+
if no_redirects:
|
|
43
|
+
return Verdict(flow, Status.REFUTED, f"the destination is fixed by {url.text!r} and redirects are disabled")
|
|
44
|
+
left = "a redirect may lead elsewhere"
|
|
45
|
+
else:
|
|
46
|
+
left = "the input chooses the path" + ("" if no_redirects else ", and a redirect may lead elsewhere")
|
|
47
|
+
if verdict.status is not Status.VULNERABILITY:
|
|
48
|
+
return verdict
|
|
49
|
+
return Verdict(flow, Status.HOTSPOT, f"the host is fixed by {url.origin!r}, but {left}")
|
|
@@ -251,7 +251,7 @@ def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
|
251
251
|
default = entry.get("default", False)
|
|
252
252
|
if not isinstance(default, bool):
|
|
253
253
|
raise TypeError(f"condition default must be true or false, got {default!r}")
|
|
254
|
-
|
|
254
|
+
condition = Condition(
|
|
255
255
|
str(entry["kind"]),
|
|
256
256
|
str(entry["text"]),
|
|
257
257
|
None if entry.get("argument") is None else str(entry["argument"]),
|
|
@@ -259,3 +259,8 @@ def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
|
259
259
|
position,
|
|
260
260
|
default,
|
|
261
261
|
)
|
|
262
|
+
if condition.kind == "host" and (
|
|
263
|
+
(condition.argument is None and condition.position is None) or condition.values or condition.default
|
|
264
|
+
):
|
|
265
|
+
raise ValueError("a host condition names the URL argument, by keyword or position, and nothing else")
|
|
266
|
+
return condition
|
|
@@ -10,7 +10,7 @@ are correlated here into one high-confidence ``exploitable-vulnerability`` findi
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from collections.abc import Iterable, Mapping
|
|
13
|
-
from dataclasses import dataclass
|
|
13
|
+
from dataclasses import dataclass, replace
|
|
14
14
|
|
|
15
15
|
from coretrace_python.dependency.graph import (
|
|
16
16
|
DIRECT,
|
|
@@ -24,6 +24,7 @@ from coretrace_python.findings.refutation import Status, Verdict, Verdicts
|
|
|
24
24
|
from coretrace_python.interprocedural import Arguments, CallSite, ExternalSymbol
|
|
25
25
|
from coretrace_python.semantic.symbols import SymbolId
|
|
26
26
|
from coretrace_python.taint import Sink, TaintFlow, TaintKind
|
|
27
|
+
from coretrace_python.taint.urls import UrlProof, redirects_disabled
|
|
27
28
|
|
|
28
29
|
Affected = Mapping[SymbolId, tuple[Advisory, ...]]
|
|
29
30
|
|
|
@@ -61,14 +62,33 @@ class ConditionCheck:
|
|
|
61
62
|
passed: str | None = None
|
|
62
63
|
|
|
63
64
|
|
|
64
|
-
def check_conditions(
|
|
65
|
-
|
|
65
|
+
def check_conditions(
|
|
66
|
+
entry: AdvisoryEntryPoint | None,
|
|
67
|
+
arguments: Arguments | None,
|
|
68
|
+
url: UrlProof | None = None,
|
|
69
|
+
passed_as: frozenset[tuple[int | None, str | None]] = frozenset(),
|
|
70
|
+
) -> ConditionCheck:
|
|
71
|
+
"""Decide ``entry``'s conditions against what the call's ``arguments`` denote and, for
|
|
72
|
+
a ``host`` condition, what ``url`` proves of the value the attacker's input is passed
|
|
73
|
+
in (``passed_as``). A host the URL's constant text fixes rules the call out when the
|
|
74
|
+
call disables redirects; otherwise a redirect may still lead to a host the attacker
|
|
75
|
+
controls, and the condition stays pending with that uncertainty."""
|
|
66
76
|
|
|
67
77
|
if entry is None:
|
|
68
78
|
return ConditionCheck()
|
|
69
79
|
met: list[Condition] = []
|
|
70
80
|
pending: list[Condition] = []
|
|
71
81
|
for condition in entry.conditions:
|
|
82
|
+
if condition.kind == "host":
|
|
83
|
+
origin = url.origin if url is not None and _passed_in(condition, passed_as) else None
|
|
84
|
+
if origin is None:
|
|
85
|
+
pending.append(condition)
|
|
86
|
+
elif redirects_disabled(arguments):
|
|
87
|
+
return ConditionCheck(tuple(met), tuple(pending), condition, repr(origin))
|
|
88
|
+
else:
|
|
89
|
+
text = f"the host is fixed by {origin!r}, but a redirect may lead to a host the attacker controls"
|
|
90
|
+
pending.append(replace(condition, text=text))
|
|
91
|
+
continue
|
|
72
92
|
given = arguments.given(condition.argument, condition.position) if condition.checkable and arguments is not None else None
|
|
73
93
|
if given is None:
|
|
74
94
|
pending.append(condition)
|
|
@@ -84,6 +104,16 @@ def check_conditions(entry: AdvisoryEntryPoint | None, arguments: Arguments | No
|
|
|
84
104
|
return ConditionCheck(tuple(met), tuple(pending))
|
|
85
105
|
|
|
86
106
|
|
|
107
|
+
def _passed_in(condition: Condition, passed_as: frozenset[tuple[int | None, str | None]]) -> bool:
|
|
108
|
+
"""Whether a value passed as ``passed_as`` is the argument ``condition`` names."""
|
|
109
|
+
|
|
110
|
+
return any(
|
|
111
|
+
(keyword is not None and keyword == condition.argument)
|
|
112
|
+
or (keyword is None and position is not None and position == condition.position)
|
|
113
|
+
for position, keyword in passed_as
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
87
117
|
def ruled_out(module: str, site: CallSite, check: ConditionCheck) -> str:
|
|
88
118
|
"""Which call a contradicted condition rules out, and why: ``app:12
|
|
89
119
|
python.yaml.load(Loader=python.yaml.SafeLoader)``."""
|
|
@@ -120,9 +150,10 @@ def correlate(
|
|
|
120
150
|
flows: Iterable[TaintFlow],
|
|
121
151
|
verdicts: Verdicts | None,
|
|
122
152
|
affected: Affected,
|
|
153
|
+
urls: Mapping[TaintFlow, UrlProof] | None = None,
|
|
123
154
|
) -> tuple[Finding, ...]:
|
|
124
155
|
"""Exploitable-vulnerability findings for the non-refuted ADVISORY flows of a function,
|
|
125
|
-
one per advisory the sink is affected by."""
|
|
156
|
+
one per advisory the sink is affected by; ``urls`` holds what each flow's URL proves."""
|
|
126
157
|
|
|
127
158
|
findings: list[Finding] = []
|
|
128
159
|
for flow in flows:
|
|
@@ -136,7 +167,7 @@ def correlate(
|
|
|
136
167
|
entry = advisory.entry_point(flow.sink.symbol)
|
|
137
168
|
if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
|
|
138
169
|
continue
|
|
139
|
-
check = check_conditions(entry, flow.sink_arguments)
|
|
170
|
+
check = check_conditions(entry, flow.sink_arguments, (urls or {}).get(flow), flow.passed_as)
|
|
140
171
|
if check.contradicted is None:
|
|
141
172
|
findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
|
|
142
173
|
return tuple(findings)
|
|
@@ -151,8 +151,10 @@ class Condition:
|
|
|
151
151
|
``argument`` condition names the argument by keyword, and by ``position`` when it may
|
|
152
152
|
be passed positionally, and lists the ``values`` that satisfy it — symbols
|
|
153
153
|
(``python.yaml.FullLoader``) or constants as Python writes them (``True``);
|
|
154
|
-
``default`` says an absent argument means a vulnerable value. A ``
|
|
155
|
-
|
|
154
|
+
``default`` says an absent argument means a vulnerable value. A ``host`` condition
|
|
155
|
+
says the attacker must choose the host of the URL passed as ``argument`` (or at
|
|
156
|
+
``position``); the engine decides it from what the URL's constant text proves. A
|
|
157
|
+
``semantic`` condition cannot be checked and is reported as pending review."""
|
|
156
158
|
|
|
157
159
|
kind: str
|
|
158
160
|
text: str
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.15.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -17,6 +17,7 @@ from typing import Any, ClassVar
|
|
|
17
17
|
|
|
18
18
|
from coretrace_python import __version__
|
|
19
19
|
from coretrace_python.abstract import ConstantPropagation, HeapAnalysis, RangeAnalysis
|
|
20
|
+
from coretrace_python.abstract.strings import ModuleStringsAnalysis
|
|
20
21
|
from coretrace_python.analysis import (
|
|
21
22
|
AnalysisContext,
|
|
22
23
|
AnalysisManager,
|
|
@@ -125,6 +126,7 @@ from coretrace_python.taint import (
|
|
|
125
126
|
escaped_templates,
|
|
126
127
|
registered_routes,
|
|
127
128
|
)
|
|
129
|
+
from coretrace_python.taint.urls import flow_url
|
|
128
130
|
|
|
129
131
|
TOOL_NAME = "coretrace-python-analyzer"
|
|
130
132
|
|
|
@@ -143,6 +145,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
|
|
|
143
145
|
DefUseAnalysis,
|
|
144
146
|
ConstantPropagation,
|
|
145
147
|
RangeAnalysis,
|
|
148
|
+
ModuleStringsAnalysis,
|
|
146
149
|
HeapAnalysis,
|
|
147
150
|
CallGraphAnalysis,
|
|
148
151
|
SummaryAnalysis,
|
|
@@ -768,14 +771,14 @@ def _analyse_module(
|
|
|
768
771
|
graph = manager.get(CallGraphAnalysis)
|
|
769
772
|
correlated: list[Finding] = []
|
|
770
773
|
if affected:
|
|
774
|
+
strings = manager.get(ModuleStringsAnalysis)
|
|
771
775
|
for function in supported:
|
|
776
|
+
flows = manager.get(TaintAnalysis, function).flows
|
|
777
|
+
ssa = manager.get(SSAAnalysis, function)
|
|
778
|
+
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
779
|
+
urls = {flow: flow_url(flow, defs, strings) for flow in flows if flow.kinds & TaintKind.ADVISORY}
|
|
772
780
|
correlated.extend(
|
|
773
|
-
correlate(
|
|
774
|
-
graph.name_of(function),
|
|
775
|
-
manager.get(TaintAnalysis, function).flows,
|
|
776
|
-
manager.get(RefutationAnalysis, function),
|
|
777
|
-
affected,
|
|
778
|
-
)
|
|
781
|
+
correlate(graph.name_of(function), flows, manager.get(RefutationAnalysis, function), affected, urls)
|
|
779
782
|
)
|
|
780
783
|
return CachedModule(
|
|
781
784
|
manager.get(EntryPointAnalysis),
|
|
@@ -13,13 +13,14 @@ from typing import ClassVar
|
|
|
13
13
|
|
|
14
14
|
from coretrace_python.analysis import AnyAnalysis
|
|
15
15
|
from coretrace_python.findings import Confidence, Finding, Severity
|
|
16
|
-
from coretrace_python.findings.refutation import RefutationAnalysis, Status
|
|
16
|
+
from coretrace_python.findings.refutation import RefutationAnalysis, Status, Verdict
|
|
17
|
+
from coretrace_python.hir import nodes
|
|
17
18
|
from coretrace_python.interprocedural import CallGraphAnalysis
|
|
18
19
|
from coretrace_python.ir.model import Call, Instruction, Symbol, Value
|
|
19
20
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
20
21
|
from coretrace_python.plugins.api import Plugin, PluginContext
|
|
21
22
|
from coretrace_python.semantic.symbols import SymbolId
|
|
22
|
-
from coretrace_python.taint import TaintAnalysis, TaintKind
|
|
23
|
+
from coretrace_python.taint import TaintAnalysis, TaintFlow, TaintKind
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class TaintDetector(Plugin):
|
|
@@ -40,6 +41,9 @@ class TaintDetector(Plugin):
|
|
|
40
41
|
if not flow.kinds & self.kind:
|
|
41
42
|
continue
|
|
42
43
|
verdict = verdicts.verdict(flow)
|
|
44
|
+
if verdict.status is Status.REFUTED:
|
|
45
|
+
continue
|
|
46
|
+
verdict = self.judge(ctx, function, flow, verdict)
|
|
43
47
|
if verdict.status is Status.REFUTED:
|
|
44
48
|
continue
|
|
45
49
|
message = f"{self.title}: {flow.source.label} input reaches {flow.sink.symbol}"
|
|
@@ -70,6 +74,13 @@ class TaintDetector(Plugin):
|
|
|
70
74
|
)
|
|
71
75
|
return findings
|
|
72
76
|
|
|
77
|
+
def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
|
|
78
|
+
"""This rule's verdict on ``flow``, given the refutation's: the same, unless the rule
|
|
79
|
+
knows more about its own sinks. The refutation's verdict, which other consumers
|
|
80
|
+
of the flow read, does not change."""
|
|
81
|
+
|
|
82
|
+
return verdict
|
|
83
|
+
|
|
73
84
|
|
|
74
85
|
def _lower(confidence: Confidence) -> Confidence:
|
|
75
86
|
return {Confidence.HIGH: Confidence.MEDIUM}.get(confidence, Confidence.LOW)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""What the constant text a URL starts with proves about its destination, and whether a
|
|
2
|
+
call follows redirects: one proof that the rules reading URLs share, each drawing its
|
|
3
|
+
own verdict from it. The text is what ``+`` and f-strings build before any other value
|
|
4
|
+
(``abstract.strings``); the proof says nothing about the rest of the URL."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from coretrace_python.abstract.strings import leading_text
|
|
13
|
+
from coretrace_python.interprocedural import Arguments
|
|
14
|
+
from coretrace_python.ir.model import Instruction, Value
|
|
15
|
+
from coretrace_python.taint.engine import TaintFlow
|
|
16
|
+
|
|
17
|
+
# ``scheme://`` and an authority ended by ``/``, ``?`` or ``#``. A backslash, which some
|
|
18
|
+
# URL parsers take for a slash, does not end it here.
|
|
19
|
+
_ORIGIN = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://[^/?#\\]+[/?#]")
|
|
20
|
+
# The same with the path fixed too: what follows is query or fragment.
|
|
21
|
+
_PATH = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://[^/?#\\]+(?:/[^?#]*)?[?#]")
|
|
22
|
+
# A reference a browser resolves on the current site: ``/`` then a character that is not
|
|
23
|
+
# a slash, a backslash, a space or a control character (``//`` and ``/\`` name another
|
|
24
|
+
# host, and a browser drops tabs and newlines), a query or a fragment, or a relative path
|
|
25
|
+
# whose first segment ends in the text without a ``:`` that would make it a scheme.
|
|
26
|
+
_RELATIVE = re.compile(r"/[^/\\\x00-\x20\x7f]|[?#]|[^/\\?#:\x00-\x20\x7f][^/\\?#:]*[/?#]")
|
|
27
|
+
# A client honours the one of these keywords it knows and rejects the other with a
|
|
28
|
+
# ``TypeError``: either way, the call follows no redirect.
|
|
29
|
+
_NO_REDIRECTS = ("allow_redirects", "follow_redirects")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class UrlProof:
|
|
34
|
+
"""What the constant ``text`` a URL starts with proves: the ``origin`` it fixes
|
|
35
|
+
(``scheme://authority`` and the character ending it), whether it fixes the path
|
|
36
|
+
too (``path_fixed``), and whether it starts a reference a browser resolves on the
|
|
37
|
+
current site (``relative``)."""
|
|
38
|
+
|
|
39
|
+
text: str
|
|
40
|
+
origin: str | None = None
|
|
41
|
+
path_fixed: bool = False
|
|
42
|
+
relative: bool = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def url_proof(text: str) -> UrlProof:
|
|
46
|
+
origin = _ORIGIN.match(text)
|
|
47
|
+
return UrlProof(
|
|
48
|
+
text,
|
|
49
|
+
origin.group(0) if origin is not None else None,
|
|
50
|
+
_PATH.match(text) is not None,
|
|
51
|
+
_RELATIVE.match(text) is not None,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def flow_url(flow: TaintFlow, defs: Mapping[Value, Instruction], strings: Mapping[str, str]) -> UrlProof:
|
|
56
|
+
"""The proof for the value ``flow`` passes to its sink, ``defs`` defining the values
|
|
57
|
+
of the function making the call. A flow reaching its sink in a callee proves nothing:
|
|
58
|
+
the value is built there."""
|
|
59
|
+
|
|
60
|
+
if flow.through is not None:
|
|
61
|
+
return UrlProof("")
|
|
62
|
+
text, _ = leading_text(flow.argument, defs, strings)
|
|
63
|
+
return url_proof(text)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def redirects_disabled(arguments: Arguments | None) -> bool:
|
|
67
|
+
"""Whether the call disables redirects explicitly (``allow_redirects=False`` or
|
|
68
|
+
``follow_redirects=False``); absent, a variable or unpacked options do not."""
|
|
69
|
+
|
|
70
|
+
return arguments is not None and any(arguments.given(name) == (True, "False") for name in _NO_REDIRECTS)
|