coretrace-python-analyzer 0.13.0__tar.gz → 0.14.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.14.0}/CHANGELOG.md +10 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/docs/plugins.md +6 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/docs/usage.md +4 -4
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/__init__.py +1 -1
- coretrace_python_analyzer-0.14.0/src/coretrace_python/abstract/strings.py +98 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +1 -1
- coretrace_python_analyzer-0.14.0/src/coretrace_python/bundled/security/ssrf/ssrf.py +64 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/engine.py +2 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/detectors.py +13 -2
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_detectors.py +4 -1
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.14.0/tests/test_ssrf_destinations.py +154 -0
- 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.14.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/README.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/advisories.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/correlation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/graph.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/provenance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/taint/engine.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/taint/models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_attacker_arguments.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_code_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_members.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_nosql_injection.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_provenance.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Precision
|
|
6
|
+
|
|
7
|
+
- `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).
|
|
8
|
+
|
|
9
|
+
### Plugins
|
|
10
|
+
|
|
11
|
+
- `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).
|
|
12
|
+
|
|
3
13
|
## 0.13.0 (2026-09-26)
|
|
4
14
|
|
|
5
15
|
### Precision
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.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,12 @@ 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` reads there how the URL is built. The refutation's
|
|
121
|
+
verdict, which other consumers of the flow such as the advisory correlation read, does
|
|
122
|
+
not change; a rule reading more analyses declares them in its `requires`.
|
|
123
|
+
|
|
118
124
|
### Security models: `ModelPlugin`
|
|
119
125
|
|
|
120
126
|
Declares sources, sinks and sanitizers for a library or a framework. A model plugin
|
|
@@ -114,7 +114,7 @@ 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
120
|
| `open-redirect` | An HTTP redirect target. |
|
|
@@ -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.14.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.14.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:…"}
|
|
@@ -436,7 +436,7 @@ project's behalf would go unseen. Without a lock file, a package is never
|
|
|
436
436
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
437
437
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
438
438
|
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
439
|
-
(`coretrace-python-analyzer 0.
|
|
439
|
+
(`coretrace-python-analyzer 0.14.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
440
440
|
`@id` changes with the advisory data a statement was decided with.
|
|
441
441
|
|
|
442
442
|
## 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,64 @@
|
|
|
1
|
+
"""Server-side request forgery: attacker-controlled input reaching a SSRF sink.
|
|
2
|
+
|
|
3
|
+
How the URL is built, with ``+`` and f-strings, can prove part of its destination. Constant
|
|
4
|
+
text fixing ``scheme://host`` and ending the authority with ``/``, ``?`` or ``#`` before
|
|
5
|
+
any input keeps the input from choosing the host; the input still chooses the path, which
|
|
6
|
+
may reach a sensitive resource of that host, and a redirect may lead elsewhere, so the
|
|
7
|
+
flow is a hotspot. With the path fixed too, the input reaching the query or fragment
|
|
8
|
+
only, and redirects disabled in the call, the destination is fixed and the flow is
|
|
9
|
+
refuted. Anything unproven, a base of unknown value or a URL built in a helper function,
|
|
10
|
+
stays a vulnerability.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from typing import ClassVar
|
|
17
|
+
|
|
18
|
+
from coretrace_python.abstract.strings import ModuleStringsAnalysis, leading_text
|
|
19
|
+
from coretrace_python.analysis import AnyAnalysis
|
|
20
|
+
from coretrace_python.findings import Severity
|
|
21
|
+
from coretrace_python.findings.refutation import Status, Verdict
|
|
22
|
+
from coretrace_python.hir import nodes
|
|
23
|
+
from coretrace_python.ir.ssa import SSAAnalysis
|
|
24
|
+
from coretrace_python.plugins import PluginContext, TaintDetector
|
|
25
|
+
from coretrace_python.taint import TaintFlow, TaintKind
|
|
26
|
+
|
|
27
|
+
# ``scheme://`` and an authority ended by ``/``, ``?`` or ``#``. A backslash, which some
|
|
28
|
+
# URL parsers take for a slash, does not end it here.
|
|
29
|
+
_FIXED_HOST = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://[^/?#\\]+[/?#]")
|
|
30
|
+
# The same with the path fixed too: what follows is query or fragment.
|
|
31
|
+
_FIXED_PATH = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://[^/?#\\]+(?:/[^?#]*)?[?#]")
|
|
32
|
+
# A client honours the one of these keywords it knows and rejects the other with a
|
|
33
|
+
# ``TypeError``: either way, the call follows no redirect.
|
|
34
|
+
_NO_REDIRECTS = ("allow_redirects", "follow_redirects")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SsrfPlugin(TaintDetector):
|
|
38
|
+
name: ClassVar[str] = "ssrf"
|
|
39
|
+
rule_id: ClassVar[str] = "ssrf"
|
|
40
|
+
kind: ClassVar[TaintKind] = TaintKind.SSRF
|
|
41
|
+
severity: ClassVar[Severity] = Severity.HIGH
|
|
42
|
+
title: ClassVar[str] = "Server-side request forgery"
|
|
43
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = TaintDetector.requires | {SSAAnalysis, ModuleStringsAnalysis}
|
|
44
|
+
|
|
45
|
+
def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
|
|
46
|
+
if flow.through is not None:
|
|
47
|
+
return verdict # the URL is built in the callee
|
|
48
|
+
ssa = ctx.get(SSAAnalysis, function)
|
|
49
|
+
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
50
|
+
text, _ = leading_text(flow.argument, defs, ctx.get(ModuleStringsAnalysis))
|
|
51
|
+
host = _FIXED_HOST.match(text)
|
|
52
|
+
if host is None:
|
|
53
|
+
return verdict
|
|
54
|
+
arguments = flow.sink_arguments
|
|
55
|
+
no_redirects = arguments is not None and any(arguments.given(name) == (True, "False") for name in _NO_REDIRECTS)
|
|
56
|
+
if _FIXED_PATH.match(text) is not None:
|
|
57
|
+
if no_redirects:
|
|
58
|
+
return Verdict(flow, Status.REFUTED, f"the destination is fixed by {text!r} and redirects are disabled")
|
|
59
|
+
left = "a redirect may lead elsewhere"
|
|
60
|
+
else:
|
|
61
|
+
left = "the input chooses the path" + ("" if no_redirects else ", and a redirect may lead elsewhere")
|
|
62
|
+
if verdict.status is not Status.VULNERABILITY:
|
|
63
|
+
return verdict
|
|
64
|
+
return Verdict(flow, Status.HOTSPOT, f"the host is fixed by {host.group(0)!r}, but {left}")
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.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,
|
|
@@ -143,6 +144,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
|
|
|
143
144
|
DefUseAnalysis,
|
|
144
145
|
ConstantPropagation,
|
|
145
146
|
RangeAnalysis,
|
|
147
|
+
ModuleStringsAnalysis,
|
|
146
148
|
HeapAnalysis,
|
|
147
149
|
CallGraphAnalysis,
|
|
148
150
|
SummaryAnalysis,
|
|
@@ -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)
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/tests/test_detectors.py
RENAMED
|
@@ -201,8 +201,11 @@ def test_shipped_plugins_load_with_their_manifests() -> None:
|
|
|
201
201
|
"xss",
|
|
202
202
|
}
|
|
203
203
|
for rule in ("sql-injection", "command-injection", "code-injection", "nosql-injection", "path-traversal", "ssrf", "xss"):
|
|
204
|
-
assert by_name[rule].requires == ("taint.flows", "findings.refutation")
|
|
205
204
|
assert by_name[rule].provides == (f"vulnerability.{rule}",)
|
|
205
|
+
for rule in ("sql-injection", "command-injection", "code-injection", "nosql-injection", "path-traversal", "xss"):
|
|
206
|
+
assert by_name[rule].requires == ("taint.flows", "findings.refutation")
|
|
207
|
+
# ``ssrf`` also reads how the URL of each flow is built.
|
|
208
|
+
assert by_name["ssrf"].requires == ("taint.flows", "findings.refutation", "ir.ssa", "abstract.module_strings")
|
|
206
209
|
assert by_name["python-stdlib-models"].provides == ("model.python-stdlib",)
|
|
207
210
|
assert by_name["weak-crypto"].provides == ("vulnerability.weak-crypto",)
|
|
208
211
|
|
|
@@ -30,7 +30,7 @@ def require_changelog() -> None:
|
|
|
30
30
|
def test_the_version_is_declared_once_and_mirrored() -> None:
|
|
31
31
|
project = tomllib.loads((REPO / "pyproject.toml").read_text(encoding="utf-8"))["project"]
|
|
32
32
|
|
|
33
|
-
assert project["version"] == coretrace_python.__version__ == "0.
|
|
33
|
+
assert project["version"] == coretrace_python.__version__ == "0.14.0"
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_the_changelog_starts_with_the_current_version() -> None:
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""An SSRF flow is judged on how its URL is built, with ``+`` and f-strings only.
|
|
2
|
+
|
|
3
|
+
A fixed host is a limited proof. When constant text fixes ``scheme://host`` and ends the
|
|
4
|
+
authority with ``/``, ``?`` or ``#`` before any input, the input cannot choose the host;
|
|
5
|
+
it still chooses the path, which may reach a sensitive resource of that host, and a
|
|
6
|
+
redirect may lead elsewhere. The flow is a hotspot. It is refuted only when the constant
|
|
7
|
+
text fixes the path too, the input reaching the query or the fragment, and the call
|
|
8
|
+
disables redirects (``allow_redirects=False``, ``follow_redirects=False``): the
|
|
9
|
+
destination is then fixed.
|
|
10
|
+
|
|
11
|
+
Every other URL stays a vulnerability: a base of unknown value (an import, a call
|
|
12
|
+
result), which may leave the authority open (``BASE_URL = "https:/"``); a constant that
|
|
13
|
+
leaves it open (no ``/`` after the host, a variable port, no scheme); ``urljoin``; a URL
|
|
14
|
+
built in a helper function. A module-level name is known text only when the module binds
|
|
15
|
+
it once, to a string literal, and never rebinds it. The advisory correlation keeps its
|
|
16
|
+
own verdict.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
from coretrace_python import engine
|
|
26
|
+
from coretrace_python.dependency import Advisory, AdvisoryEntryPoint, dump_advisories
|
|
27
|
+
from coretrace_python.findings import Finding, Severity
|
|
28
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
29
|
+
from coretrace_python.source import SourceManager
|
|
30
|
+
|
|
31
|
+
PLUGINS = [engine.BUNDLED_PLUGINS]
|
|
32
|
+
|
|
33
|
+
APP = (
|
|
34
|
+
"import httpx\nimport requests\nfrom urllib.parse import urljoin\nfrom flask import Flask, request\n\n"
|
|
35
|
+
"app = Flask(__name__)\n"
|
|
36
|
+
"API = 'https://api.example.com/'\n\n"
|
|
37
|
+
"@app.route('/users/<uid>')\n"
|
|
38
|
+
"def fetch(uid):\n"
|
|
39
|
+
" return {call}\n"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check(text: str) -> tuple[Finding, ...]:
|
|
44
|
+
return engine.check(SourceManager().add_source("app.py", text), PLUGINS)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def ssrf(text: str) -> list[tuple[int, str]]:
|
|
48
|
+
return [(f.span.start_line, f.metadata["verdict"]) for f in check(text) if f.rule_id == "ssrf"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize(
|
|
52
|
+
"call, verdict",
|
|
53
|
+
[
|
|
54
|
+
("requests.get('https://api.example.com/users/' + uid, timeout=5)", "hotspot"),
|
|
55
|
+
("requests.get(f'https://api.example.com/users/{uid}', timeout=5)", "hotspot"),
|
|
56
|
+
("requests.get(API + 'users/' + uid, timeout=5)", "hotspot"),
|
|
57
|
+
# ``path='@evil.com'`` makes ``https://api.example.com@evil.com``: evil.com is the host.
|
|
58
|
+
("requests.get('https://api.example.com' + request.args['path'], timeout=5)", "vulnerability"),
|
|
59
|
+
# An absolute or scheme-relative ``path`` replaces the host.
|
|
60
|
+
("requests.get(urljoin('https://api.example.com/', request.args['path']), timeout=5)", "vulnerability"),
|
|
61
|
+
("requests.get(request.args['url'], timeout=5)", "vulnerability"),
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
def test_constant_text_fixing_the_host_makes_the_flow_a_hotspot(call: str, verdict: str) -> None:
|
|
65
|
+
assert ssrf(APP.format(call=call)) == [(11, verdict)]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.parametrize(
|
|
69
|
+
"prelude, call",
|
|
70
|
+
[
|
|
71
|
+
# An imported base may be anything: ``"https:/"`` would let ``uid`` choose the host.
|
|
72
|
+
("from settings import BASE_URL\n", "requests.get(f'{BASE_URL}/users/{uid}', timeout=5)"),
|
|
73
|
+
("BASE = 'https:/'\n", "requests.get(f'{BASE}/{uid}', timeout=5)"),
|
|
74
|
+
("BASE = get_base()\n", "requests.get(BASE + '/users/' + uid, timeout=5)"),
|
|
75
|
+
("\n", "requests.get('https://api.example.com:' + uid + '/users/', timeout=5)"),
|
|
76
|
+
("\n", "requests.get('//api.example.com/users/' + uid, timeout=5)"),
|
|
77
|
+
("\n", "requests.get('https://api.example.com/users/%s' % uid, timeout=5)"),
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
def test_an_unknown_or_incomplete_base_stays_a_vulnerability(prelude: str, call: str) -> None:
|
|
81
|
+
text = APP.format(call=call).replace("API = 'https://api.example.com/'\n", prelude)
|
|
82
|
+
|
|
83
|
+
assert ssrf(text) == [(11, "vulnerability")]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize(
|
|
87
|
+
"rebinding",
|
|
88
|
+
[
|
|
89
|
+
"API = 'https://b.example.com/'\n",
|
|
90
|
+
"def configure():\n global API\n API = input()\n",
|
|
91
|
+
"for API in ('https://a.example.com/', 'https://b.example.com/'):\n pass\n",
|
|
92
|
+
"def API():\n return 'https://a.example.com/'\n",
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
def test_a_module_string_bound_more_than_once_is_unknown(rebinding: str) -> None:
|
|
96
|
+
text = APP.format(call="requests.get(API + 'users/' + uid, timeout=5)") + "\n" + rebinding
|
|
97
|
+
|
|
98
|
+
assert ssrf(text) == [(11, "vulnerability")]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.parametrize(
|
|
102
|
+
"call, verdict",
|
|
103
|
+
[
|
|
104
|
+
("requests.get('https://api.example.com/search?q=' + request.args['q'], allow_redirects=False, timeout=5)", None),
|
|
105
|
+
("httpx.get(f'https://api.example.com/search?q={uid}', follow_redirects=False, timeout=5)", None),
|
|
106
|
+
("requests.get('https://api.example.com/search#' + uid, allow_redirects=False, timeout=5)", None),
|
|
107
|
+
# Redirects followed: the fixed destination may send the request elsewhere.
|
|
108
|
+
("requests.get('https://api.example.com/search?q=' + request.args['q'], timeout=5)", "hotspot"),
|
|
109
|
+
# Redirects disabled, but the input chooses the path.
|
|
110
|
+
("requests.get('https://api.example.com/users/' + uid, allow_redirects=False, timeout=5)", "hotspot"),
|
|
111
|
+
# Unpacked options may enable redirects again.
|
|
112
|
+
("requests.get('https://api.example.com/search?q=' + uid, **{'allow_redirects': True})", "hotspot"),
|
|
113
|
+
],
|
|
114
|
+
)
|
|
115
|
+
def test_only_a_fixed_destination_without_redirects_is_refuted(call: str, verdict: str | None) -> None:
|
|
116
|
+
assert ssrf(APP.format(call=call)) == ([(11, verdict)] if verdict else [])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_the_evidence_names_the_fixed_text_and_what_the_input_still_chooses() -> None:
|
|
120
|
+
(finding,) = [
|
|
121
|
+
f for f in check(APP.format(call="requests.get('https://api.example.com/users/' + uid, timeout=5)"))
|
|
122
|
+
if f.rule_id == "ssrf"
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
assert "'https://api.example.com/'" in finding.metadata["evidence"]
|
|
126
|
+
assert "path" in finding.metadata["evidence"] and "redirect" in finding.metadata["evidence"]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_a_url_built_in_a_helper_function_stays_a_vulnerability() -> None:
|
|
130
|
+
text = APP.format(call="get(uid)") + "\ndef get(path):\n return requests.get('https://api.example.com/' + path, timeout=5)\n"
|
|
131
|
+
|
|
132
|
+
assert ssrf(text) == [(11, "vulnerability")]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_the_advisory_correlation_keeps_its_own_verdict(tmp_path: Path) -> None:
|
|
136
|
+
advisory = Advisory(
|
|
137
|
+
"CVE-2099-0801",
|
|
138
|
+
"requests",
|
|
139
|
+
"<3",
|
|
140
|
+
"get sends credentials to the host of a crafted URL",
|
|
141
|
+
Severity.HIGH,
|
|
142
|
+
entry_points=(AdvisoryEntryPoint(SymbolId("python.requests.get"), "commit abc1234"),),
|
|
143
|
+
modules=("requests",),
|
|
144
|
+
)
|
|
145
|
+
(tmp_path / "requirements.txt").write_text("requests==2.31.0\nflask==3.0.0\n", encoding="utf-8")
|
|
146
|
+
(tmp_path / "advisories.json").write_text(dump_advisories((advisory,)), encoding="utf-8")
|
|
147
|
+
(tmp_path / "app.py").write_text(
|
|
148
|
+
APP.format(call="requests.get('https://api.example.com/users/' + uid, timeout=5)"), encoding="utf-8"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
findings = engine.analyze_project(tmp_path, PLUGINS).findings
|
|
152
|
+
|
|
153
|
+
verdicts = {f.rule_id: f.metadata["verdict"] for f in findings if f.rule_id in ("ssrf", "exploitable-vulnerability")}
|
|
154
|
+
assert verdicts == {"ssrf": "hotspot", "exploitable-vulnerability": "vulnerability"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Server-side request forgery: attacker-controlled input reaching a SSRF sink."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import ClassVar
|
|
6
|
-
|
|
7
|
-
from coretrace_python.findings import Severity
|
|
8
|
-
from coretrace_python.plugins import TaintDetector
|
|
9
|
-
from coretrace_python.taint import TaintKind
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SsrfPlugin(TaintDetector):
|
|
13
|
-
name: ClassVar[str] = "ssrf"
|
|
14
|
-
rule_id: ClassVar[str] = "ssrf"
|
|
15
|
-
kind: ClassVar[TaintKind] = TaintKind.SSRF
|
|
16
|
-
severity: ClassVar[Severity] = Severity.HIGH
|
|
17
|
-
title: ClassVar[str] = "Server-side request forgery"
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coretrace_python_analyzer-0.13.0 → coretrace_python_analyzer-0.14.0}/examples/canonical_symbols.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|