coretrace-python-analyzer 0.11.0__tar.gz → 0.12.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.11.0 → coretrace_python_analyzer-0.12.0}/CHANGELOG.md +13 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/docs/plugins.md +2 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/docs/usage.md +4 -3
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +10 -6
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/cli/cli_models.py +4 -4
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/flask/flask_models.py +12 -9
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +5 -3
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +10 -5
- coretrace_python_analyzer-0.12.0/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +20 -0
- coretrace_python_analyzer-0.12.0/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +9 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/engine.py +7 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/interprocedural/callgraph.py +14 -12
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/lowering.py +1 -1
- coretrace_python_analyzer-0.12.0/src/coretrace_python/semantic/__init__.py +9 -0
- coretrace_python_analyzer-0.12.0/src/coretrace_python/semantic/symbols.py +177 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/taint/__init__.py +4 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/taint/engine.py +69 -6
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/taint/models.py +16 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_analysis_manager.py +3 -2
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_detectors.py +2 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_django_http_clients.py +1 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_docs.py +1 -1
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_frameworks.py +47 -2
- coretrace_python_analyzer-0.12.0/tests/test_members.py +206 -0
- coretrace_python_analyzer-0.12.0/tests/test_nosql_injection.py +206 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_operator_sources.py +6 -4
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_packaging.py +2 -2
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_release.py +1 -1
- coretrace_python_analyzer-0.11.0/src/coretrace_python/semantic/__init__.py +0 -9
- coretrace_python_analyzer-0.11.0/src/coretrace_python/semantic/symbols.py +0 -87
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/README.md +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cache.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/advisories.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/correlation.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/graph.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/dependency/vex.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/provenance.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/loader.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/taint/templates.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_advisory_evidence.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_argument_conditions.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_attacker_arguments.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_cache.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_code_injection.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_django_safe_values.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_interprocedural_taint.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_module_level.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_plugins.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_project.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_project_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_project_validators.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_provenance.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_read_entry_points.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_safe_arguments.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_ssrf_targets.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_summary_sanitizers.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_taint.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_template_escaping.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_tornado_bottle_models.py +0 -0
- {coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/tests/test_vex.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.0 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Detection
|
|
6
|
+
|
|
7
|
+
- A module-level name bound to an attribute, an item or a call of another module-level name resolves in functions, as the same value does in a function body. `cursor = conn.cursor()` after `conn = sqlite3.connect(...)`, `db = client.shop` and `run = os.system` used to leave the calls made through them unknown, so a query built from request data and executed through a module-level cursor was not reported. Names resolve in statement order. The regression corpus is unchanged (#155).
|
|
8
|
+
- `nosql-injection` reports attacker input that may hold a structure reaching a NoSQL sink, such as a MongoDB filter, where a JSON body giving `{"$ne": null}` matches any value. Only input that may hold a structure carries `NOSQL`: JSON, the raw bodies a `json.loads` decodes, files, WebSocket messages and standard input. Text cannot hold an operator. The text sources of Flask, bottle and tornado, the URL parameters of their routes, the command line, the environment and `input()` no longer carry `NOSQL`. Neither does an entry-point parameter annotated with scalars (`str`, `Optional[int]`, `Annotated[str, Query()]`, `list[str]`, …), which FastAPI validates. Two limits remain. Django and aiohttp request objects stay tainted as a whole. The keys of a dict are not followed, so a string concatenated into `$where` is not reported. The engine ships no NoSQL sink: model plugins declare them (#158).
|
|
9
|
+
|
|
10
|
+
### Plugins
|
|
11
|
+
|
|
12
|
+
- `TEXT_KINDS` holds every kind but `NOSQL`: what text input carries. A model plugin declares its text sources with it (#158).
|
|
13
|
+
|
|
14
|
+
- A `Members(symbol, dynamic=None, defined=(), typed=())` model says what the attributes and items of a class's instances give, for libraries whose objects give others by names the project chooses. A pymongo client gives the database it is dotted or indexed with, a database gives a collection. The names used to end up in the symbol (`MongoClient.shop.users.find`), or an item vanished into its container's symbol (`MongoClient.find`), so no model could list them. With the model, every path to a collection denotes the collection class, the one a parameter annotated `Collection` denotes: `client.shop.users`, `client["shop"]["users"]`, `client.get_database("shop").get_collection("users")`, a module-level `db = client.shop`, an attribute inherited from the client class. A sink on `Collection.find` covers them all. Symbol resolution now depends on these models, which the engine provides to every module and worker (#157).
|
|
15
|
+
|
|
3
16
|
## 0.11.0 (2026-09-25)
|
|
4
17
|
|
|
5
18
|
### Detection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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
|
|
@@ -140,7 +140,7 @@ class BottleModels(ModelPlugin):
|
|
|
140
140
|
|
|
141
141
|
| Model | Declares |
|
|
142
142
|
|---|---|
|
|
143
|
-
| `Source(symbol, label, kinds=ALL)` | A call or attribute whose value is attacker-controlled; `label` names it in messages (`http`, `stdin`, `argv`, `http-response`). `kinds` restricts what it can inject. |
|
|
143
|
+
| `Source(symbol, label, kinds=ALL)` | A call or attribute whose value is attacker-controlled; `label` names it in messages (`http`, `stdin`, `argv`, `http-response`). `kinds` restricts what it can inject. Text input, such as a form field, a header or a URL parameter, carries `TEXT_KINDS`, every kind but `NOSQL`: a string cannot hold a query operator. |
|
|
144
144
|
| `Sink(symbol, kinds, positions=(), keywords=())` | A call dangerous for `kinds`. `positions` limits a kind to the given argument indexes, and `keywords` to the given keyword names; a restricted kind reaches only the arguments listed there. SQL sinks read the statement only, and the SSRF sinks of HTTP clients read the URL, by position or as `url=`. A starred argument or `**kwargs` reaches no restricted kind. |
|
|
145
145
|
| `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`, including when the call is inside a project function the data goes through. |
|
|
146
146
|
| `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
|
|
@@ -152,6 +152,7 @@ class BottleModels(ModelPlugin):
|
|
|
152
152
|
| `TemplateRender(symbol, position=0, keyword="template_name")` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string`. What it returns carries no `HTML` when the name is written in the call and the project's template escapes everything it prints (see the usage guide). |
|
|
153
153
|
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe for `kinds`, such as `re.fullmatch`. A flow guarded by it is refuted when it carries no other kind into the sink, and is a hotspot otherwise: a redirect validator does not make a shell command safe. `symbol` may be a function of the project, by its project symbol (`python.hc.accounts.views._allow_redirect`), which then counts wherever it is called, in its own module too. |
|
|
154
154
|
| `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
|
|
155
|
+
| `Members(symbol, dynamic=None, defined=(), typed=())` | What the attributes and items of an instance of the class `symbol` give, for objects that give others by name. `typed` maps an attribute to the class of what it gives, called or not, since a call denotes its callee: pymongo's `get_database` gives a `Database`, as Flask-PyMongo's `db` property does. `dynamic`, when set, is the class of every item and of every attribute outside `defined` and `typed`: a pymongo client gives a `Database` by name (`client.shop`, `client["shop"]`), a database a `Collection`. Every path to a collection then denotes the collection class, so a sink on `Collection.find` covers them all. Without `dynamic`, an attribute keeps its path and an item its container's symbol, as for any class. |
|
|
155
156
|
|
|
156
157
|
Two plugins may describe the same symbol. An identical model is registered once; two
|
|
157
158
|
`Sink` models merge their kinds, positions and keywords, so a plugin can add `SQL` to a sink
|
|
@@ -112,6 +112,7 @@ variable is not treated that way yet.
|
|
|
112
112
|
| `command-injection` | Shell or process execution (`os.system`, `subprocess` with a string, …). |
|
|
113
113
|
| `code-injection` | The code `eval` or `exec` runs; the globals and locals passed with it are data. `dangerous-eval` reports every such call, this rule the ones attacker input reaches. |
|
|
114
114
|
| `sql-injection` | A database statement (`cursor.execute`, SQLAlchemy `text`, Django `raw`, …). Parameters of a parameterised query are not statements. |
|
|
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. Limits: a Django or aiohttp request object is tainted as a whole, so `request.GET` counts like `request.body`; 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. |
|
|
115
116
|
| `path-traversal` | A file system path (`open`, `send_file`, `os.remove`, …). |
|
|
116
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
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). |
|
|
@@ -240,7 +241,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
240
241
|
```json
|
|
241
242
|
{
|
|
242
243
|
"schema_version": 1,
|
|
243
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
244
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.12.0"},
|
|
244
245
|
"root": "/home/me/project",
|
|
245
246
|
"findings": [
|
|
246
247
|
{
|
|
@@ -272,7 +273,7 @@ bytes. A single-file check lists no components.
|
|
|
272
273
|
|
|
273
274
|
```json
|
|
274
275
|
"tool": {
|
|
275
|
-
"name": "coretrace-python-analyzer", "version": "0.
|
|
276
|
+
"name": "coretrace-python-analyzer", "version": "0.12.0",
|
|
276
277
|
"components": [
|
|
277
278
|
{"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
|
|
278
279
|
{"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
|
|
@@ -435,7 +436,7 @@ project's behalf would go unseen. Without a lock file, a package is never
|
|
|
435
436
|
`not_affected`. The document's `@id` derives from its statements, so two runs with the
|
|
436
437
|
same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
|
|
437
438
|
cannot know it. Its `tooling` names the analyzer and each component with its digest
|
|
438
|
-
(`coretrace-python-analyzer 0.
|
|
439
|
+
(`coretrace-python-analyzer 0.12.0; curated-advisories 2026.09.25 (sha256:…)`), so the
|
|
439
440
|
`@id` changes with the advisory data a statement was decided with.
|
|
440
441
|
|
|
441
442
|
## Large projects
|
|
@@ -8,13 +8,15 @@ from typing import ClassVar
|
|
|
8
8
|
|
|
9
9
|
from coretrace_python.plugins import ModelPlugin
|
|
10
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
11
|
-
from coretrace_python.taint import EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
11
|
+
from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
12
12
|
|
|
13
13
|
_METHODS = ("route", "get", "post", "put", "delete", "patch")
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Text, which cannot hold a query operator; JSON, raw bodies and files may hold a structure.
|
|
15
|
+
_TEXT_ATTRIBUTES = (
|
|
16
|
+
"query", "forms", "params", "headers", "cookies", "GET", "POST",
|
|
16
17
|
"url", "path", "fullpath", "query_string", "get_cookie", "get_header",
|
|
17
18
|
)
|
|
19
|
+
_PAYLOAD_ATTRIBUTES = ("json", "body", "files")
|
|
18
20
|
_FIRST_ONLY = lambda kind: ((kind, (0,)),)
|
|
19
21
|
|
|
20
22
|
|
|
@@ -25,9 +27,11 @@ def _sym(path: str) -> SymbolId:
|
|
|
25
27
|
class BottleModels(ModelPlugin):
|
|
26
28
|
name: ClassVar[str] = "bottle-models"
|
|
27
29
|
models: ClassVar[tuple[Model, ...]] = (
|
|
28
|
-
|
|
29
|
-
*(EntryPoint(_sym(f"bottle.
|
|
30
|
-
*(
|
|
30
|
+
# A route's parameters are segments of its URL.
|
|
31
|
+
*(EntryPoint(_sym(f"bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
|
|
32
|
+
*(EntryPoint(_sym(f"bottle.Bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
|
|
33
|
+
*(Source(_sym(f"bottle.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
|
|
34
|
+
*(Source(_sym(f"bottle.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
|
|
31
35
|
Sink(_sym("bottle.redirect"), TaintKind.REDIRECT, _FIRST_ONLY(TaintKind.REDIRECT)),
|
|
32
36
|
# ``static_file(filename, root)``: the root is the application's own.
|
|
33
37
|
Sink(_sym("bottle.static_file"), TaintKind.PATH, _FIRST_ONLY(TaintKind.PATH)),
|
|
@@ -6,11 +6,11 @@ from typing import ClassVar
|
|
|
6
6
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
|
-
from coretrace_python.taint import EntryPoint, Model, TaintKind
|
|
9
|
+
from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, TaintKind
|
|
10
10
|
|
|
11
|
-
# A command-line tool is expected to open the paths it is given: argv input
|
|
12
|
-
#
|
|
13
|
-
ARGV_KINDS =
|
|
11
|
+
# A command-line tool is expected to open the paths it is given: argv input is text,
|
|
12
|
+
# without PATH.
|
|
13
|
+
ARGV_KINDS = TEXT_KINDS & ~TaintKind.PATH
|
|
14
14
|
|
|
15
15
|
_COMMANDS = (
|
|
16
16
|
"click.command",
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
9
|
from coretrace_python.taint import (
|
|
10
|
+
TEXT_KINDS,
|
|
10
11
|
AuthorizationGuard,
|
|
11
12
|
EntryPoint,
|
|
12
13
|
Model,
|
|
@@ -17,10 +18,10 @@ from coretrace_python.taint import (
|
|
|
17
18
|
TaintKind,
|
|
18
19
|
)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
21
|
+
# Strings, or mappings of strings: text, which cannot hold a query operator.
|
|
22
|
+
_TEXT_ATTRIBUTES = ("args", "form", "values", "cookies", "headers", "url", "full_path", "path", "query_string")
|
|
23
|
+
# JSON, and the raw bodies and files a ``json.loads`` may decode into a structure.
|
|
24
|
+
_PAYLOAD_ATTRIBUTES = ("json", "get_json", "data", "get_data", "stream", "files")
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
_TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
|
|
@@ -33,11 +34,13 @@ def _sym(path: str) -> SymbolId:
|
|
|
33
34
|
class FlaskModels(ModelPlugin):
|
|
34
35
|
name: ClassVar[str] = "flask-models"
|
|
35
36
|
models: ClassVar[tuple[Model, ...]] = (
|
|
36
|
-
*(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
*(Source(_sym(f"flask.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
|
|
38
|
+
*(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
|
|
39
|
+
# A route's parameters are segments of its URL.
|
|
40
|
+
EntryPoint(_sym("flask.Flask.route"), "http", TEXT_KINDS),
|
|
41
|
+
EntryPoint(_sym("flask.Blueprint.route"), "http", TEXT_KINDS),
|
|
42
|
+
RouteRegistrar(_sym("flask.Flask.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
|
|
43
|
+
RouteRegistrar(_sym("flask.Blueprint.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
|
|
41
44
|
Sink(_sym("flask.render_template_string"), TaintKind.HTML),
|
|
42
45
|
Sink(_sym("flask.make_response"), TaintKind.HTML),
|
|
43
46
|
Sink(_sym("flask.Response"), TaintKind.HTML),
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
9
|
from coretrace_python.taint import (
|
|
10
|
+
TEXT_KINDS,
|
|
10
11
|
Model,
|
|
11
12
|
SafeArgument,
|
|
12
13
|
Sanitizer,
|
|
@@ -16,7 +17,7 @@ from coretrace_python.taint import (
|
|
|
16
17
|
Validator,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
|
-
_ENVIRONMENT_KINDS =
|
|
20
|
+
_ENVIRONMENT_KINDS = TEXT_KINDS & ~(TaintKind.COMMAND | TaintKind.PATH)
|
|
20
21
|
# SafeLoader and BaseLoader build plain data only, in Python and in C, under every spelling.
|
|
21
22
|
_SAFE_YAML_LOADERS = (
|
|
22
23
|
"python.yaml.SafeLoader",
|
|
@@ -38,13 +39,14 @@ def _sym(path: str) -> SymbolId:
|
|
|
38
39
|
class PythonStdlibModels(ModelPlugin):
|
|
39
40
|
name: ClassVar[str] = "python-stdlib-models"
|
|
40
41
|
models: ClassVar[tuple[Model, ...]] = (
|
|
41
|
-
|
|
42
|
+
# A line of text; the stream may be a payload ``json.load`` decodes.
|
|
43
|
+
Source(_sym("builtins.input"), "stdin", TEXT_KINDS),
|
|
42
44
|
Source(_sym("sys.stdin"), "stdin"),
|
|
43
45
|
# Operator-controlled inputs. A command-line tool is expected to open the paths
|
|
44
46
|
# it is given; the environment is set by whoever runs the program, so a command
|
|
45
47
|
# or a path built from it is not an injection; the output of a local process is
|
|
46
48
|
# not a path either, but a downloaded script piped into a shell is a real flaw.
|
|
47
|
-
Source(_sym("sys.argv"), "argv",
|
|
49
|
+
Source(_sym("sys.argv"), "argv", TEXT_KINDS & ~TaintKind.PATH),
|
|
48
50
|
Source(_sym("os.environ"), "environment", _ENVIRONMENT_KINDS),
|
|
49
51
|
Source(_sym("subprocess.run.stdout"), "process-output", _PROCESS_OUTPUT_KINDS),
|
|
50
52
|
Source(_sym("subprocess.check_output"), "process-output", _PROCESS_OUTPUT_KINDS),
|
|
@@ -8,12 +8,13 @@ from typing import ClassVar
|
|
|
8
8
|
|
|
9
9
|
from coretrace_python.plugins import ModelPlugin
|
|
10
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
11
|
-
from coretrace_python.taint import EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
11
|
+
from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
12
12
|
|
|
13
13
|
_HANDLERS = ("tornado.web.RequestHandler", "tornado.websocket.WebSocketHandler")
|
|
14
|
-
|
|
14
|
+
# Text, which cannot hold a query operator; the request object holds the raw body too.
|
|
15
|
+
_TEXT_METHODS = (
|
|
15
16
|
"get_argument", "get_arguments", "get_query_argument", "get_query_arguments",
|
|
16
|
-
"get_body_argument", "get_body_arguments", "get_cookie", "
|
|
17
|
+
"get_body_argument", "get_body_arguments", "get_cookie", "path_args", "path_kwargs",
|
|
17
18
|
)
|
|
18
19
|
_CLIENTS = ("tornado.httpclient.AsyncHTTPClient.fetch", "tornado.httpclient.HTTPClient.fetch")
|
|
19
20
|
_TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
|
|
@@ -26,8 +27,12 @@ def _sym(path: str) -> SymbolId:
|
|
|
26
27
|
class TornadoModels(ModelPlugin):
|
|
27
28
|
name: ClassVar[str] = "tornado-models"
|
|
28
29
|
models: ClassVar[tuple[Model, ...]] = (
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
# A request handler's parameters are segments of its URL; a WebSocket handler also
|
|
31
|
+
# receives the messages, raw payloads a ``json.loads`` may decode.
|
|
32
|
+
EntryPoint(_sym("tornado.web.RequestHandler"), "http", TEXT_KINDS),
|
|
33
|
+
EntryPoint(_sym("tornado.websocket.WebSocketHandler"), "http"),
|
|
34
|
+
*(Source(_sym(f"{handler}.{method}"), "http", TEXT_KINDS) for handler in _HANDLERS for method in _TEXT_METHODS),
|
|
35
|
+
*(Source(_sym(f"{handler}.request"), "http") for handler in _HANDLERS),
|
|
31
36
|
*(Sink(_sym(f"{handler}.write"), TaintKind.HTML) for handler in _HANDLERS),
|
|
32
37
|
*(Sink(_sym(f"{handler}.finish"), TaintKind.HTML) for handler in _HANDLERS),
|
|
33
38
|
*(Sink(_sym(f"{handler}.redirect"), TaintKind.REDIRECT, _TARGET_ONLY) for handler in _HANDLERS),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""NoSQL injection: attacker input that may hold a structure reaching a NOSQL sink, such
|
|
2
|
+
as a MongoDB filter, where a JSON body giving ``{"$ne": null}`` matches any value. Text
|
|
3
|
+
input carries no ``NOSQL``: a string cannot hold an operator. The engine ships no NOSQL
|
|
4
|
+
sink; model plugins declare them."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from coretrace_python.findings import Severity
|
|
11
|
+
from coretrace_python.plugins import TaintDetector
|
|
12
|
+
from coretrace_python.taint import TaintKind
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NoSqlInjectionPlugin(TaintDetector):
|
|
16
|
+
name: ClassVar[str] = "nosql-injection"
|
|
17
|
+
rule_id: ClassVar[str] = "nosql-injection"
|
|
18
|
+
kind: ClassVar[TaintKind] = TaintKind.NOSQL
|
|
19
|
+
severity: ClassVar[Severity] = Severity.HIGH
|
|
20
|
+
title: ClassVar[str] = "NoSQL injection"
|
{coretrace_python_analyzer-0.11.0 → coretrace_python_analyzer-0.12.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -109,7 +109,7 @@ from coretrace_python.reporters import Report
|
|
|
109
109
|
from coretrace_python.semantic import SEMANTIC_ANALYSES
|
|
110
110
|
from coretrace_python.semantic.imports import ImportAnalysis, ImportResolutionError, ImportTable
|
|
111
111
|
from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeError
|
|
112
|
-
from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId
|
|
112
|
+
from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis, SymbolId
|
|
113
113
|
from coretrace_python.source import SourceFile, SourceId, SourceManager, SourceSpan, decode_text
|
|
114
114
|
from coretrace_python.taint import (
|
|
115
115
|
EntryPoint,
|
|
@@ -257,6 +257,7 @@ def build_manager(
|
|
|
257
257
|
manager = _register_all(module)
|
|
258
258
|
table = (models or SecurityModelRegistry()).freeze()
|
|
259
259
|
manager.provide(SecurityModelAnalysis, table)
|
|
260
|
+
manager.provide(MembersAnalysis, table.members_by_class())
|
|
260
261
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
261
262
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
262
263
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -311,6 +312,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
|
|
|
311
312
|
registry = load_plugins(plugin_roots, manager)
|
|
312
313
|
table = plugin_models(loaded.plugin for loaded in registry)
|
|
313
314
|
manager.provide(SecurityModelAnalysis, table)
|
|
315
|
+
manager.provide(MembersAnalysis, table.members_by_class())
|
|
314
316
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
315
317
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
316
318
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -402,8 +404,10 @@ def analyze_project(
|
|
|
402
404
|
advisories, origins = _merge_advisories(_contributions(registry, plugins, root, file_advisories))
|
|
403
405
|
affected = affected_symbols(dependencies, advisories)
|
|
404
406
|
models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
|
|
407
|
+
members = models.members_by_class()
|
|
405
408
|
for manager in managers.values():
|
|
406
409
|
manager.provide(SecurityModelAnalysis, models)
|
|
410
|
+
manager.provide(MembersAnalysis, members)
|
|
407
411
|
manager.provide(DependencyAnalysis, dependencies)
|
|
408
412
|
|
|
409
413
|
imports: dict[str, ImportTable] = {}
|
|
@@ -698,8 +702,10 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
|
698
702
|
models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
|
|
699
703
|
routes = _decode_routes(batch.routes)
|
|
700
704
|
escaped = frozenset(batch.escaped)
|
|
705
|
+
members = models.members_by_class()
|
|
701
706
|
for manager in managers.values():
|
|
702
707
|
manager.provide(SecurityModelAnalysis, models)
|
|
708
|
+
manager.provide(MembersAnalysis, members)
|
|
703
709
|
manager.provide(DependencyAnalysis, dependencies)
|
|
704
710
|
manager.provide(RegisteredRoutes, routes)
|
|
705
711
|
manager.provide(EscapedTemplates, escaped)
|
|
@@ -176,12 +176,12 @@ class CallGraph:
|
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
def derive_symbols(
|
|
179
|
-
function: FunctionIR, initial: Mapping[Value, SymbolId] | None = None
|
|
179
|
+
function: FunctionIR, table: SymbolTable, initial: Mapping[Value, SymbolId] | None = None
|
|
180
180
|
) -> dict[Value, SymbolId]:
|
|
181
|
-
"""Symbols of values: ``Symbol`` results, attributes and items of symbol values,
|
|
182
|
-
results of calling a symbol (``sqlite3.connect(p)`` denotes
|
|
183
|
-
and the values a ``with`` on such a result binds. Known
|
|
184
|
-
parameters only through ``initial``, their annotated classes."""
|
|
181
|
+
"""Symbols of values: ``Symbol`` results, attributes and items of symbol values, as
|
|
182
|
+
``table`` types them, results of calling a symbol (``sqlite3.connect(p)`` denotes
|
|
183
|
+
``python.sqlite3.connect``) and the values a ``with`` on such a result binds. Known
|
|
184
|
+
functions derive nothing; parameters only through ``initial``, their annotated classes."""
|
|
185
185
|
|
|
186
186
|
symbols: dict[Value, SymbolId] = dict(initial or {})
|
|
187
187
|
changed = True
|
|
@@ -195,11 +195,9 @@ def derive_symbols(
|
|
|
195
195
|
if isinstance(instruction, Symbol):
|
|
196
196
|
symbol = instruction.symbol_id
|
|
197
197
|
elif isinstance(instruction, GetAttr) and instruction.object in symbols:
|
|
198
|
-
symbol = symbols[instruction.object]
|
|
198
|
+
symbol = table.attribute(symbols[instruction.object], instruction.attribute)
|
|
199
199
|
elif isinstance(instruction, GetItem) and instruction.object in symbols:
|
|
200
|
-
|
|
201
|
-
# carries the container's symbol, so its methods resolve.
|
|
202
|
-
symbol = symbols[instruction.object]
|
|
200
|
+
symbol = table.item(symbols[instruction.object])
|
|
203
201
|
elif isinstance(instruction, Call | WithEnter):
|
|
204
202
|
origin = (
|
|
205
203
|
instruction.callee if isinstance(instruction, Call) else instruction.context
|
|
@@ -263,6 +261,8 @@ def resolve_targets(
|
|
|
263
261
|
owner: str | None = None,
|
|
264
262
|
typed: Mapping[Value, str] | None = None,
|
|
265
263
|
bases: Mapping[str, SymbolId] | None = None,
|
|
264
|
+
*,
|
|
265
|
+
table: SymbolTable,
|
|
266
266
|
) -> tuple[dict[Value, Target], dict[Value, SymbolId]]:
|
|
267
267
|
"""Map every callee value of ``function`` to its target, and every symbol value.
|
|
268
268
|
``nested`` names the functions defined inside this one (``outer.inner``);
|
|
@@ -271,7 +271,8 @@ def resolve_targets(
|
|
|
271
271
|
call to ``App.__init__``, ``app.run()`` and ``self.run()`` calls to ``App.run``.
|
|
272
272
|
``bases`` maps a module class to the symbol of its base: an attribute the class does
|
|
273
273
|
not define is inherited, so ``self.get_argument`` in a ``RequestHandler`` subclass
|
|
274
|
-
denotes ``tornado.web.RequestHandler.get_argument``.
|
|
274
|
+
denotes ``tornado.web.RequestHandler.get_argument``. ``table`` says what the
|
|
275
|
+
attributes and items of a class a ``Members`` model describes give."""
|
|
275
276
|
|
|
276
277
|
module = scopes.module_scope
|
|
277
278
|
classes = classes or {}
|
|
@@ -294,8 +295,8 @@ def resolve_targets(
|
|
|
294
295
|
class_name = instance_of[instruction.object]
|
|
295
296
|
base = (bases or {}).get(class_name)
|
|
296
297
|
if base is not None and instruction.attribute not in classes.get(class_name, ()):
|
|
297
|
-
inherited[instruction.result] =
|
|
298
|
-
symbols = derive_symbols(function, inherited)
|
|
298
|
+
inherited[instruction.result] = table.attribute(base, instruction.attribute)
|
|
299
|
+
symbols = derive_symbols(function, table, inherited)
|
|
299
300
|
targets: dict[Value, Target] = {
|
|
300
301
|
value: ExternalSymbol(symbol) for value, symbol in symbols.items()
|
|
301
302
|
}
|
|
@@ -427,6 +428,7 @@ class CallGraphAnalysis(Analysis[CallGraph]):
|
|
|
427
428
|
owner,
|
|
428
429
|
_typed_with_module_classes(function, ssa, classes),
|
|
429
430
|
bases,
|
|
431
|
+
table=table,
|
|
430
432
|
)
|
|
431
433
|
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
432
434
|
found: list[CallSite] = []
|
|
@@ -141,7 +141,7 @@ class _FunctionLowerer:
|
|
|
141
141
|
return self.symbols.resolve(self.scope.id, node.identifier)
|
|
142
142
|
if isinstance(node, nodes.Attribute):
|
|
143
143
|
parent = self.imported_symbol(node.value)
|
|
144
|
-
return
|
|
144
|
+
return self.symbols.attribute(parent, node.name) if parent is not None else None
|
|
145
145
|
return None
|
|
146
146
|
|
|
147
147
|
def expression(self, node: nodes.Expression) -> Value:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Semantic analyses computed from PyHIR: scopes, imports and symbols."""
|
|
2
|
+
|
|
3
|
+
from coretrace_python.semantic.imports import ImportAnalysis
|
|
4
|
+
from coretrace_python.semantic.scopes import ScopeAnalysis
|
|
5
|
+
from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis
|
|
6
|
+
|
|
7
|
+
SEMANTIC_ANALYSES = (ScopeAnalysis, ImportAnalysis, MembersAnalysis, SymbolAnalysis)
|
|
8
|
+
|
|
9
|
+
__all__ = ["SEMANTIC_ANALYSES", "ImportAnalysis", "MembersAnalysis", "ScopeAnalysis", "SymbolAnalysis"]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Symbol analysis: resolve names to canonical identities (architecture §4.3)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from coretrace_python.analysis import Analysis, AnalysisContext, AnyAnalysis
|
|
12
|
+
from coretrace_python.hir import nodes
|
|
13
|
+
from coretrace_python.semantic.identity import SymbolId
|
|
14
|
+
from coretrace_python.semantic.imports import ImportAnalysis, ImportTable
|
|
15
|
+
from coretrace_python.semantic.scopes import ResolutionKind, ScopeAnalysis, ScopeId, ScopeTable
|
|
16
|
+
|
|
17
|
+
BUILTIN_NAMES = frozenset(name for name in dir(builtins) if not name.startswith("_"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Members:
|
|
22
|
+
"""What the attributes and items of an instance of ``symbol`` give, for a class whose
|
|
23
|
+
instances give other objects by name, as a pymongo client gives the database it is
|
|
24
|
+
dotted or indexed with (``client.shop``, ``client['shop']``).
|
|
25
|
+
|
|
26
|
+
``typed`` maps an attribute to the class of what it gives, called or not, since a
|
|
27
|
+
call denotes its callee: ``get_database`` gives a database, as a ``db`` property does.
|
|
28
|
+
``dynamic``, when set, is the class of every item and of every attribute outside
|
|
29
|
+
``defined`` and ``typed``. Otherwise an attribute keeps its path and an item its
|
|
30
|
+
container's symbol, as for any other class."""
|
|
31
|
+
|
|
32
|
+
symbol: SymbolId
|
|
33
|
+
dynamic: SymbolId | None = None
|
|
34
|
+
defined: tuple[str, ...] = ()
|
|
35
|
+
typed: tuple[tuple[str, SymbolId], ...] = ()
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
# Sorted: the models' repr is part of the cache key, the same in every process.
|
|
39
|
+
object.__setattr__(self, "defined", tuple(sorted(set(self.defined))))
|
|
40
|
+
|
|
41
|
+
def attribute(self, name: str) -> SymbolId:
|
|
42
|
+
for attribute, gives in self.typed:
|
|
43
|
+
if attribute == name:
|
|
44
|
+
return gives
|
|
45
|
+
if self.dynamic is not None and name not in self.defined:
|
|
46
|
+
return self.dynamic
|
|
47
|
+
return self.symbol.attribute(name)
|
|
48
|
+
|
|
49
|
+
def item(self) -> SymbolId:
|
|
50
|
+
return self.symbol if self.dynamic is None else self.dynamic
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SymbolTable:
|
|
54
|
+
"""Resolve names to canonical symbols through scopes, imports, builtins and the
|
|
55
|
+
module-level names bound to a value denoting a symbol (``app = Flask(__name__)``,
|
|
56
|
+
``cursor = conn.cursor()``). ``members`` holds the ``Members`` models by class."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
scopes: ScopeTable,
|
|
61
|
+
imports: ImportTable,
|
|
62
|
+
instances: Mapping[str, SymbolId] | None = None,
|
|
63
|
+
members: Mapping[SymbolId, Members] | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._scopes = scopes
|
|
66
|
+
self._imports = imports
|
|
67
|
+
self._instances = MappingProxyType(dict(instances or {}))
|
|
68
|
+
self._members = MappingProxyType(dict(members or {}))
|
|
69
|
+
|
|
70
|
+
def attribute(self, symbol: SymbolId, name: str) -> SymbolId:
|
|
71
|
+
"""The symbol of attribute ``name`` of what ``symbol`` denotes: its path, unless
|
|
72
|
+
a ``Members`` model describes the class."""
|
|
73
|
+
|
|
74
|
+
members = self._members.get(symbol)
|
|
75
|
+
return symbol.attribute(name) if members is None else members.attribute(name)
|
|
76
|
+
|
|
77
|
+
def item(self, symbol: SymbolId) -> SymbolId:
|
|
78
|
+
"""The symbol of an item of what ``symbol`` denotes: the container's own, so its
|
|
79
|
+
methods resolve (``request.files['f'].save``), unless a ``Members`` model says."""
|
|
80
|
+
|
|
81
|
+
members = self._members.get(symbol)
|
|
82
|
+
return symbol if members is None else members.item()
|
|
83
|
+
|
|
84
|
+
def resolve(self, scope_id: ScopeId, name: str) -> SymbolId | None:
|
|
85
|
+
resolution = self._scopes.resolve(scope_id, name)
|
|
86
|
+
if resolution.kind is ResolutionKind.UNBOUND:
|
|
87
|
+
return SymbolId(f"python.builtins.{name}") if name in BUILTIN_NAMES else None
|
|
88
|
+
assert resolution.scope is not None
|
|
89
|
+
imported = self._imports.bindings(resolution.scope).get(name)
|
|
90
|
+
if imported is not None:
|
|
91
|
+
return imported
|
|
92
|
+
if resolution.scope == self._scopes.module_scope.id:
|
|
93
|
+
return self._instances.get(name)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def resolve_expression(self, scope_id: ScopeId, node: nodes.Expression) -> SymbolId | None:
|
|
97
|
+
"""The symbol of a name or attribute chain, or of the callee of a call."""
|
|
98
|
+
|
|
99
|
+
if isinstance(node, nodes.Name):
|
|
100
|
+
return self.resolve(scope_id, node.identifier)
|
|
101
|
+
if isinstance(node, nodes.Attribute):
|
|
102
|
+
parent = self.resolve_expression(scope_id, node.value)
|
|
103
|
+
return self.attribute(parent, node.name) if parent is not None else None
|
|
104
|
+
if isinstance(node, nodes.Call):
|
|
105
|
+
return self.resolve_expression(scope_id, node.callee)
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def analyze_symbols(
|
|
110
|
+
scopes: ScopeTable,
|
|
111
|
+
imports: ImportTable,
|
|
112
|
+
module: nodes.Module | None = None,
|
|
113
|
+
members: Mapping[SymbolId, Members] | None = None,
|
|
114
|
+
) -> SymbolTable:
|
|
115
|
+
table = SymbolTable(scopes, imports, members=members)
|
|
116
|
+
if module is None:
|
|
117
|
+
return table
|
|
118
|
+
instances: dict[str, SymbolId] = {}
|
|
119
|
+
module_scope = scopes.module_scope.id
|
|
120
|
+
for statement in module.body:
|
|
121
|
+
if isinstance(statement, nodes.Assign) and isinstance(statement.target, nodes.Name):
|
|
122
|
+
symbol = _denoted(table, module_scope, statement.value)
|
|
123
|
+
if symbol is not None:
|
|
124
|
+
instances[statement.target.identifier] = symbol
|
|
125
|
+
# Later bindings resolve through this one, in statement order.
|
|
126
|
+
table = SymbolTable(scopes, imports, instances, members)
|
|
127
|
+
return table
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _denoted(table: SymbolTable, scope_id: ScopeId, node: nodes.Expression) -> SymbolId | None:
|
|
131
|
+
"""What a value denotes, as a function body derives it (``derive_symbols``): a
|
|
132
|
+
resolved name or an attribute of one, the result of calling one, or an item of one,
|
|
133
|
+
which carries its container's symbol (``os.environ['HOME']``) unless a ``Members``
|
|
134
|
+
model says what its items are."""
|
|
135
|
+
|
|
136
|
+
if isinstance(node, nodes.Subscript):
|
|
137
|
+
container = _denoted(table, scope_id, node.value)
|
|
138
|
+
return table.item(container) if container is not None else None
|
|
139
|
+
if isinstance(node, nodes.Attribute):
|
|
140
|
+
parent = _denoted(table, scope_id, node.value)
|
|
141
|
+
return table.attribute(parent, node.name) if parent is not None else None
|
|
142
|
+
if isinstance(node, nodes.Call):
|
|
143
|
+
return _denoted(table, scope_id, node.callee)
|
|
144
|
+
return table.resolve_expression(scope_id, node)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class MembersAnalysis(Analysis[Mapping[SymbolId, Members]]):
|
|
148
|
+
"""The ``Members`` models by class, provided by the engine from the security models;
|
|
149
|
+
none on its own, so every attribute keeps its path."""
|
|
150
|
+
|
|
151
|
+
name: ClassVar[str] = "semantic.members"
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def compute(cls, ctx: AnalysisContext) -> Mapping[SymbolId, Members]:
|
|
155
|
+
return MappingProxyType({})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class SymbolAnalysis(Analysis[SymbolTable]):
|
|
159
|
+
name: ClassVar[str] = "semantic.symbols"
|
|
160
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis, MembersAnalysis})
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def compute(cls, ctx: AnalysisContext) -> SymbolTable:
|
|
164
|
+
return analyze_symbols(
|
|
165
|
+
ctx.get(ScopeAnalysis), ctx.get(ImportAnalysis), ctx.module, ctx.get(MembersAnalysis)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = [
|
|
170
|
+
"BUILTIN_NAMES",
|
|
171
|
+
"Members",
|
|
172
|
+
"MembersAnalysis",
|
|
173
|
+
"SymbolAnalysis",
|
|
174
|
+
"SymbolId",
|
|
175
|
+
"SymbolTable",
|
|
176
|
+
"analyze_symbols",
|
|
177
|
+
]
|