coretrace-python-analyzer 0.2.0__tar.gz → 0.3.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.2.0 → coretrace_python_analyzer-0.3.0}/CHANGELOG.md +16 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/PKG-INFO +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/docs/plugins.md +19 -5
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/docs/usage.md +27 -3
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/pyproject.toml +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/__init__.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cache.py +10 -7
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/engine.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/loader.py +18 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/taint/engine.py +16 -3
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/taint/models.py +33 -12
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_cache.py +20 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_interprocedural_taint.py +13 -2
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_plugins.py +45 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_project.py +32 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_release.py +1 -1
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_taint.py +69 -3
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/.github/workflows/ci.yml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/.github/workflows/release.yml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/.gitignore +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/LICENSE +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/NOTICE +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/README.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/docs/architecture.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/examples/add.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/examples/canonical_symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/__main__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/abstract/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/abstract/constants.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/abstract/heap.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/abstract/ranges.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/abstract/values.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/analysis/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/analysis/manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/analysis/provider.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cfg/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cfg/builder.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cfg/dominance.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cfg/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cli.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dataflow/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dataflow/lattice.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dataflow/solver.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/advisories.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/correlation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/graph.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/policy.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/dependency/sbom.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/baseline.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/coverage.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/refutation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/findings/suppressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/frontend/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/frontend/parser.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/hir/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/hir/nodes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/hir/visitors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/defuse.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/lowering.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/printer.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/ir/ssa.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/api.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/manifest.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/registry.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/plugins/secrets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/reporters/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/reporters/json_format.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/reporters/report.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/reporters/sarif.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/reporters/text.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/semantic/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/semantic/identity.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/semantic/imports.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/semantic/scopes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/semantic/symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/source/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/source/manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/source/model.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/taint/__init__.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/taint/routes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/Chatbot.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/DjangoGoat.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/InsecureGram.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/NPL-IA.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/dvpwa.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/healthchecks.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/httpie.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/luigi.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/my_gdb.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/network-topologer.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/pygoat.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/vulnerable-flask.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/expected/yt-fts.json +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/regression/repositories.toml +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_advisory_feeds.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_aiohttp_models.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_analysis_manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_architecture.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_audit_detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_baseline.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_cfg.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_class_keywords_with_targets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_cli.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_closures.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_config_secrets_coverage.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_constants.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_correlation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_credential_context.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_dataflow.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_dependencies.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_detectors.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_discovery.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_django_http_clients.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_django_routes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_docs.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_dominance.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_emit_ir.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_exceptions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_factories_match_loop_else.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_fail_on.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_fastapi_cli_precision.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_frameworks.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_frontend_gaps.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_heap.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_hex_digests.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_hir_builder.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_imports.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_interprocedural.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_ir.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_match_patterns.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_nonlocal_writeback.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_operator_sources.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_packaging.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_parallel.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_parser.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_plaintext_credentials.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_pyir_imports.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_real_code_precision.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_redirect_targets.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_refutation.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_refutation_evidence.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_regression.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_relative_paths.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_remaining_limits.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_reporters.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_scale.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_scopes.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_secrets_sbom.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_self_state.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_source_manager.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_ssa.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_suppressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_symbol_resolution.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_symbols.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_syntactic_rules.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_syntax_control_flow.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_syntax_coverage.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_syntax_expressions.py +0 -0
- {coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/tests/test_tornado_bottle_models.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 (2026-09-22)
|
|
4
|
+
|
|
5
|
+
### Plugins
|
|
6
|
+
|
|
7
|
+
- Two plugins may declare the same symbol: an identical model is registered once and sinks merge their kinds and positions; a real conflict names both plugins instead of failing the run on the first duplicate (#92).
|
|
8
|
+
- A plugin's entrypoint may be a package, `<module>/__init__.py`, so a plugin can split its tables across files and import them relatively (#93).
|
|
9
|
+
- The cache key covers every file of a plugin directory, not only `.py` and `.toml`, so rules or advisories shipped as data invalidate cached results when they change (#94).
|
|
10
|
+
|
|
11
|
+
### Analysis
|
|
12
|
+
|
|
13
|
+
- New taint kinds: `NOSQL` in `TaintKind.ALL`; `LOG` and `PII` outside it, carried only by the values a model marks, so a NoSQL-injection, log-injection or personal-data detector no longer has to share a kind with another rule (#96).
|
|
14
|
+
|
|
15
|
+
### Precision
|
|
16
|
+
|
|
17
|
+
- A `Sanitizer` declared on a function of the analysed project takes precedence over the summary derived from its body, in the same file and across files, so a code base can declare its own validation layer (#95).
|
|
18
|
+
|
|
3
19
|
## 0.2.0 (2026-09-06)
|
|
4
20
|
|
|
5
21
|
### Adoption in an existing repository
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -38,7 +38,12 @@ class = "SensitiveLoggingPlugin"
|
|
|
38
38
|
| `plugin_api` | The range of plugin API versions the plugin supports. The current API is version 1. |
|
|
39
39
|
| `requires` | Capabilities the plugin needs; a mismatch with the class's `requires` is a manifest error. |
|
|
40
40
|
| `provides` | Capabilities the plugin offers, for documentation and conflict detection. |
|
|
41
|
-
| `entrypoint` | The module
|
|
41
|
+
| `entrypoint` | The module (without `.py`, next to the manifest) and the class in it. |
|
|
42
|
+
|
|
43
|
+
The entrypoint module may be a package, `<module>/__init__.py`, when a plugin grows past
|
|
44
|
+
one file: its submodules are reached through relative imports (`from .tables import
|
|
45
|
+
celery`). Two plugins may use the same module name; each is imported under a name derived
|
|
46
|
+
from its path.
|
|
42
47
|
|
|
43
48
|
## Symbols
|
|
44
49
|
|
|
@@ -101,10 +106,14 @@ class CommandInjectionPlugin(TaintDetector):
|
|
|
101
106
|
```
|
|
102
107
|
|
|
103
108
|
`confidence` defaults to high; a hotspot, a flow behind a guard that mentions the value
|
|
104
|
-
or an authorization decorator, is reported at medium. The taint kinds
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
or an authorization decorator, is reported at medium. The taint kinds every source
|
|
110
|
+
carries by default (`TaintKind.ALL`) are `SQL`, `NOSQL`, `COMMAND`, `HTML`, `PATH`,
|
|
111
|
+
`SSRF`, `CODE`, `DESERIALIZATION`, `REDIRECT` and `ADVISORY` (APIs affected by
|
|
112
|
+
advisories). Three kinds are outside `ALL`, so only a value a model marks with them
|
|
113
|
+
reaches their sinks: `CREDENTIAL`, which credential-named parameters carry so that
|
|
114
|
+
ordinary input reaching a database write is not a plaintext credential; `LOG`, for
|
|
115
|
+
values that must not be written to a log as they are; and `PII`, for personal data
|
|
116
|
+
that must not leave the application through a log, a third party or plain storage.
|
|
108
117
|
|
|
109
118
|
### Security models: `ModelPlugin`
|
|
110
119
|
|
|
@@ -142,6 +151,11 @@ class BottleModels(ModelPlugin):
|
|
|
142
151
|
| `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe, such as `re.fullmatch`; a flow guarded by it is refuted. |
|
|
143
152
|
| `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
|
|
144
153
|
|
|
154
|
+
Two plugins may describe the same symbol. An identical model is registered once; two
|
|
155
|
+
`Sink` models merge their kinds and positions, so a plugin can add `SQL` to a sink
|
|
156
|
+
another plugin declared for `COMMAND`; any other difference is a conflict that stops
|
|
157
|
+
the analysis with a `ModelError` naming both plugins.
|
|
158
|
+
|
|
145
159
|
A model plugin may also carry `advisories`, a tuple of `Advisory` values
|
|
146
160
|
(`coretrace_python.dependency`) with the package, the vulnerable range and the affected
|
|
147
161
|
symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
|
|
@@ -198,7 +198,7 @@ the directory of the checked file; a path outside it is printed as it is.
|
|
|
198
198
|
```json
|
|
199
199
|
{
|
|
200
200
|
"schema_version": 1,
|
|
201
|
-
"tool": {"name": "coretrace-python-analyzer", "version": "0.
|
|
201
|
+
"tool": {"name": "coretrace-python-analyzer", "version": "0.3.0"},
|
|
202
202
|
"root": "/home/me/project",
|
|
203
203
|
"findings": [
|
|
204
204
|
{
|
|
@@ -283,8 +283,9 @@ coretrace-python-analyzer --check src/ --sbom sbom.json --policy security/policy
|
|
|
283
283
|
## Large projects
|
|
284
284
|
|
|
285
285
|
`--cache DIR` keeps the results of a directory check on disk, one entry per module,
|
|
286
|
-
keyed by the module's source, the tool
|
|
287
|
-
|
|
286
|
+
keyed by the module's source, the tool version, every file of every plugin directory
|
|
287
|
+
(code, manifest and data alike), the security models, the advisories, the dependency
|
|
288
|
+
graph and the modules it imports. On the next run an unchanged
|
|
288
289
|
module is served from the cache, so editing one file re-analyses that file and its
|
|
289
290
|
importers only. Entries are plain data; an unreadable entry is recomputed.
|
|
290
291
|
|
|
@@ -335,6 +336,29 @@ coretrace-python-analyzer --check src/ --plugins ./coretrace-plugins
|
|
|
335
336
|
coretrace-python-analyzer --check src/ --no-bundled-plugins --plugins ./coretrace-plugins
|
|
336
337
|
```
|
|
337
338
|
|
|
339
|
+
### Your own validation layer
|
|
340
|
+
|
|
341
|
+
A code base that already validates its input has functions the analyzer cannot know are
|
|
342
|
+
safe: a `clean()` that returns its argument looks, from its body, like a function that
|
|
343
|
+
passes taint through. Declare them in a model plugin by their project symbol,
|
|
344
|
+
`python.<module>.<function>`, and the analyzer takes your word over what it derived:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
from coretrace_python.plugins import ModelPlugin
|
|
348
|
+
from coretrace_python.semantic.symbols import SymbolId
|
|
349
|
+
from coretrace_python.taint import Sanitizer, TaintKind, Validator
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class HouseModels(ModelPlugin):
|
|
353
|
+
name = "house-models"
|
|
354
|
+
models = (
|
|
355
|
+
# the result of app.validation.clean is safe for commands, whatever the body does
|
|
356
|
+
Sanitizer(SymbolId("python.app.validation.clean"), TaintKind.COMMAND),
|
|
357
|
+
# a flow behind ``if app.validation.is_slug(value):`` is refuted
|
|
358
|
+
Validator(SymbolId("python.app.validation.is_slug")),
|
|
359
|
+
)
|
|
360
|
+
```
|
|
361
|
+
|
|
338
362
|
## Continuous integration
|
|
339
363
|
|
|
340
364
|
The exit status gates the job and the SARIF report feeds code scanning. On GitHub
|
{coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/cache.py
RENAMED
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import json
|
|
16
16
|
import os
|
|
17
|
-
from collections.abc import
|
|
17
|
+
from collections.abc import Mapping
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from types import MappingProxyType
|
|
@@ -64,15 +64,18 @@ def fingerprint(*parts: str) -> str:
|
|
|
64
64
|
return digest.hexdigest()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def directory_fingerprint(directory: Path
|
|
68
|
-
"""A digest of
|
|
67
|
+
def directory_fingerprint(directory: Path) -> str:
|
|
68
|
+
"""A digest of every file under ``directory``, so an edited plugin misses: its code,
|
|
69
|
+
its manifest and the rule or advisory data it ships in any format. Bytecode and
|
|
70
|
+
hidden files are not part of what the plugin does."""
|
|
69
71
|
|
|
70
|
-
wanted = tuple(suffixes)
|
|
71
72
|
parts: list[str] = []
|
|
72
73
|
for path in sorted(directory.rglob("*")):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
relative = path.relative_to(directory)
|
|
75
|
+
if not path.is_file() or any(p == "__pycache__" or p.startswith(".") for p in relative.parts):
|
|
76
|
+
continue
|
|
77
|
+
parts.append(str(relative))
|
|
78
|
+
parts.append(path.read_bytes().hex())
|
|
76
79
|
return fingerprint(*parts)
|
|
77
80
|
|
|
78
81
|
|
{coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/src/coretrace_python/engine.py
RENAMED
|
@@ -257,7 +257,7 @@ def load_plugins(plugin_roots: Sequence[Path], manager: AnalysisManager) -> Plug
|
|
|
257
257
|
def plugin_models(plugins: Iterable[Plugin]) -> ModelTable:
|
|
258
258
|
models = SecurityModelRegistry()
|
|
259
259
|
for plugin in plugins:
|
|
260
|
-
models.register(*plugin.models)
|
|
260
|
+
models.register(*plugin.models, origin=plugin.name)
|
|
261
261
|
return models.freeze()
|
|
262
262
|
|
|
263
263
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import importlib.util
|
|
7
|
+
import sys
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
@@ -63,16 +64,31 @@ def discover_plugins(root: Path, manager: AnalysisManager) -> tuple[LoadedPlugin
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def _import_entrypoint(directory: Path, manifest: PluginManifest) -> type[Plugin]:
|
|
66
|
-
|
|
67
|
+
"""Import the entrypoint, a module ``<name>.py`` or a package ``<name>/__init__.py``.
|
|
68
|
+
|
|
69
|
+
The module name derives from the path, so two plugins may both call their module
|
|
70
|
+
``models``; a package is registered in ``sys.modules`` under that name before it
|
|
71
|
+
runs, which is what its relative imports resolve through."""
|
|
72
|
+
|
|
73
|
+
package_path = directory / manifest.entrypoint.module / "__init__.py"
|
|
74
|
+
is_package = package_path.is_file()
|
|
75
|
+
module_path = package_path if is_package else directory / f"{manifest.entrypoint.module}.py"
|
|
67
76
|
if not module_path.is_file():
|
|
68
77
|
raise ManifestError(
|
|
69
78
|
f"{directory / MANIFEST_FILENAME}: entrypoint module"
|
|
70
79
|
f" {manifest.entrypoint.module!r} not found"
|
|
71
80
|
)
|
|
72
81
|
unique = hashlib.sha1(str(module_path.resolve()).encode()).hexdigest()[:12]
|
|
73
|
-
|
|
82
|
+
name = f"coretrace_plugin_{unique}"
|
|
83
|
+
spec = importlib.util.spec_from_file_location(
|
|
84
|
+
name,
|
|
85
|
+
module_path,
|
|
86
|
+
submodule_search_locations=[str(module_path.parent)] if is_package else None,
|
|
87
|
+
)
|
|
74
88
|
assert spec is not None and spec.loader is not None
|
|
75
89
|
module = importlib.util.module_from_spec(spec)
|
|
90
|
+
if is_package:
|
|
91
|
+
sys.modules[name] = module
|
|
76
92
|
spec.loader.exec_module(module)
|
|
77
93
|
|
|
78
94
|
candidate = getattr(module, manifest.entrypoint.class_name, None)
|
|
@@ -147,8 +147,10 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
147
147
|
project: SummaryIndex | None = None,
|
|
148
148
|
heap: HeapFacts | None = None,
|
|
149
149
|
seeds: Mapping[HeapLocation, Taint] | None = None,
|
|
150
|
+
module: str = "",
|
|
150
151
|
) -> None:
|
|
151
152
|
self.name = name
|
|
153
|
+
self.module = module
|
|
152
154
|
self.function = function
|
|
153
155
|
self.models = models
|
|
154
156
|
self.graph = graph
|
|
@@ -294,9 +296,10 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
294
296
|
through = target.symbol.canonical_name.removeprefix("python.")
|
|
295
297
|
bound = () if project.static else self.receiver(call, project.name)
|
|
296
298
|
arguments = (*(self.deep(r, state) for r in bound), *arguments)
|
|
297
|
-
|
|
299
|
+
result = self.known(
|
|
298
300
|
project, through, arguments, keywords, everything, call, flows, state, (), bound
|
|
299
301
|
)
|
|
302
|
+
return self.declared_clean(target.symbol, result)
|
|
300
303
|
symbol = target.symbol
|
|
301
304
|
if not self.modelled(symbol):
|
|
302
305
|
# ``get_conn().execute`` derived ``app.database.get_conn.execute``; what
|
|
@@ -312,9 +315,10 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
312
315
|
*arguments,
|
|
313
316
|
*(self.deep(value, state) for value in captured),
|
|
314
317
|
)
|
|
315
|
-
|
|
318
|
+
result = self.known(
|
|
316
319
|
summary, target.name, arguments, keywords, everything, call, flows, state, captured, bound
|
|
317
320
|
)
|
|
321
|
+
return self.declared_clean(project_symbol(self.module, target.name), result)
|
|
318
322
|
returned = self.returned_symbol(call)
|
|
319
323
|
if returned is not None:
|
|
320
324
|
return self.external(returned, everything, call, state, flows)
|
|
@@ -369,6 +373,13 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
369
373
|
made = self.defs.get(call.callee)
|
|
370
374
|
return made.captured if isinstance(made, MakeFunction) else ()
|
|
371
375
|
|
|
376
|
+
def declared_clean(self, symbol: SymbolId, result: Taint) -> Taint:
|
|
377
|
+
"""A ``Sanitizer`` declared on a project function is the project's own knowledge
|
|
378
|
+
of what its result carries; it wins over what the summary derived from the body."""
|
|
379
|
+
|
|
380
|
+
sanitizer = self.models.sanitizer(symbol)
|
|
381
|
+
return result if sanitizer is None else result.without(sanitizer.kinds)
|
|
382
|
+
|
|
372
383
|
def modelled(self, symbol: SymbolId) -> bool:
|
|
373
384
|
return (
|
|
374
385
|
self.models.sink(symbol) is not None
|
|
@@ -730,8 +741,9 @@ def propagate_taint(
|
|
|
730
741
|
project: SummaryIndex | None = None,
|
|
731
742
|
heap: HeapFacts | None = None,
|
|
732
743
|
seeds: Mapping[HeapLocation, Taint] | None = None,
|
|
744
|
+
module: str = "",
|
|
733
745
|
) -> TaintFacts:
|
|
734
|
-
problem = _TaintProblem(name, function, models, graph, summaries, parameters, project, heap, seeds)
|
|
746
|
+
problem = _TaintProblem(name, function, models, graph, summaries, parameters, project, heap, seeds, module)
|
|
735
747
|
solution = solve(problem, cfg)
|
|
736
748
|
taints: dict[Key, Taint] = {}
|
|
737
749
|
flows: list[TaintFlow] = []
|
|
@@ -794,6 +806,7 @@ class TaintAnalysis(FunctionAnalysis[TaintFacts]):
|
|
|
794
806
|
ctx.get(ProjectSummaries),
|
|
795
807
|
heap,
|
|
796
808
|
seeds,
|
|
809
|
+
ctx.module.name,
|
|
797
810
|
)
|
|
798
811
|
|
|
799
812
|
|
|
@@ -30,10 +30,14 @@ class TaintKind(Flag):
|
|
|
30
30
|
ADVISORY = auto()
|
|
31
31
|
DESERIALIZATION = auto()
|
|
32
32
|
REDIRECT = auto()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
33
|
+
NOSQL = auto()
|
|
34
|
+
ALL = SQL | COMMAND | HTML | PATH | SSRF | CODE | ADVISORY | DESERIALIZATION | REDIRECT | NOSQL
|
|
35
|
+
# Outside ALL on purpose: only the values a model marks carry them, so a database
|
|
36
|
+
# write reached by ordinary input is not a plaintext credential, and ordinary input
|
|
37
|
+
# reaching a logger is neither log forging nor a personal-data leak.
|
|
36
38
|
CREDENTIAL = auto()
|
|
39
|
+
LOG = auto()
|
|
40
|
+
PII = auto()
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
class ModelError(Exception):
|
|
@@ -69,6 +73,11 @@ class Sink:
|
|
|
69
73
|
kinds &= ~restricted
|
|
70
74
|
return kinds
|
|
71
75
|
|
|
76
|
+
def merged(self, other: Sink) -> Sink:
|
|
77
|
+
"""This sink with ``other``'s kinds and positions added."""
|
|
78
|
+
|
|
79
|
+
return Sink(self.symbol, self.kinds | other.kinds, self.positions + other.positions)
|
|
80
|
+
|
|
72
81
|
|
|
73
82
|
@dataclass(frozen=True)
|
|
74
83
|
class Sanitizer:
|
|
@@ -251,11 +260,7 @@ class ModelTable:
|
|
|
251
260
|
merged = {sink.symbol: sink for sink in self.sinks}
|
|
252
261
|
for sink in sinks:
|
|
253
262
|
current = merged.get(sink.symbol)
|
|
254
|
-
merged[sink.symbol] = (
|
|
255
|
-
Sink(sink.symbol, current.kinds | sink.kinds, current.positions + sink.positions)
|
|
256
|
-
if current is not None
|
|
257
|
-
else sink
|
|
258
|
-
)
|
|
263
|
+
merged[sink.symbol] = sink if current is None else current.merged(sink)
|
|
259
264
|
return ModelTable(
|
|
260
265
|
self.sources,
|
|
261
266
|
tuple(merged.values()),
|
|
@@ -279,15 +284,31 @@ class SecurityModelRegistry:
|
|
|
279
284
|
|
|
280
285
|
def __init__(self) -> None:
|
|
281
286
|
self._models: dict[tuple[type[Model], SymbolId], Model] = {}
|
|
287
|
+
self._origins: dict[tuple[type[Model], SymbolId], str | None] = {}
|
|
288
|
+
|
|
289
|
+
def register(self, *models: Model, origin: str | None = None) -> None:
|
|
290
|
+
"""Add models; ``origin`` names the plugin so a conflict can name both sides.
|
|
291
|
+
|
|
292
|
+
Two plugins may describe the same symbol: an identical model is ignored, sinks
|
|
293
|
+
merge their kinds and positions, any other difference is a conflict."""
|
|
282
294
|
|
|
283
|
-
def register(self, *models: Model) -> None:
|
|
284
295
|
for model in models:
|
|
285
296
|
key = (type(model), model.symbol)
|
|
286
|
-
|
|
297
|
+
current = self._models.get(key)
|
|
298
|
+
if current is None:
|
|
299
|
+
self._models[key] = model
|
|
300
|
+
self._origins[key] = origin
|
|
301
|
+
elif current == model:
|
|
302
|
+
continue
|
|
303
|
+
elif isinstance(model, Sink) and isinstance(current, Sink):
|
|
304
|
+
self._models[key] = current.merged(model)
|
|
305
|
+
else:
|
|
306
|
+
registered = self._origins[key]
|
|
307
|
+
sides = f" by {registered!r} and {origin!r}" if registered and origin else ""
|
|
287
308
|
raise ModelError(
|
|
288
|
-
f"{type(model).__name__.lower()} model for {model.symbol}
|
|
309
|
+
f"{type(model).__name__.lower()} model for {model.symbol}"
|
|
310
|
+
f" is registered twice{sides} with different values"
|
|
289
311
|
)
|
|
290
|
-
self._models[key] = model
|
|
291
312
|
|
|
292
313
|
def freeze(self) -> ModelTable:
|
|
293
314
|
models = list(self._models.values())
|
|
@@ -13,6 +13,7 @@ Expected to remain red until ``coretrace_python.cache`` and the ``--cache`` opti
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
+
import shutil
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
|
|
18
19
|
import pytest
|
|
@@ -119,6 +120,25 @@ def test_keys_depend_on_dependency_files(tmp_path: Path) -> None:
|
|
|
119
120
|
assert all(after[name] != before[name] for name in before)
|
|
120
121
|
|
|
121
122
|
|
|
123
|
+
def test_keys_depend_on_every_plugin_file(tmp_path: Path) -> None:
|
|
124
|
+
root = standard(tmp_path)
|
|
125
|
+
plugins = tmp_path / "plugins"
|
|
126
|
+
shutil.copytree(
|
|
127
|
+
PLUGINS / "syntax" / "dangerous_eval",
|
|
128
|
+
plugins / "dangerous_eval",
|
|
129
|
+
ignore=shutil.ignore_patterns("__pycache__"),
|
|
130
|
+
)
|
|
131
|
+
before = engine.analyze_project(root, [plugins]).keys
|
|
132
|
+
(plugins / "dangerous_eval" / "rules.json").write_text('{"v": 1}', encoding="utf-8")
|
|
133
|
+
after = engine.analyze_project(root, [plugins]).keys
|
|
134
|
+
(plugins / "dangerous_eval" / "__pycache__").mkdir(exist_ok=True)
|
|
135
|
+
(plugins / "dangerous_eval" / "__pycache__" / "x.pyc").write_bytes(b"\0")
|
|
136
|
+
with_bytecode = engine.analyze_project(root, [plugins]).keys
|
|
137
|
+
|
|
138
|
+
assert all(after[name] != before[name] for name in before)
|
|
139
|
+
assert with_bytecode == after
|
|
140
|
+
|
|
141
|
+
|
|
122
142
|
# --------------------------------------------------------------------------- reuse
|
|
123
143
|
|
|
124
144
|
|
|
@@ -50,12 +50,12 @@ def require_interprocedural_taint() -> None:
|
|
|
50
50
|
pytest.fail("TaintAnalysis does not consume the call graph and summaries yet")
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def facts(source_text: str, name: str = "run") -> TaintFacts:
|
|
53
|
+
def facts(source_text: str, name: str = "run", *extra: Sanitizer) -> TaintFacts:
|
|
54
54
|
module = build_hir(SourceManager().add_source("ip.py", source_text))
|
|
55
55
|
manager = AnalysisManager(module)
|
|
56
56
|
manager.register(*engine.ALL_ANALYSES)
|
|
57
57
|
registry = SecurityModelRegistry()
|
|
58
|
-
registry.register(*MODELS)
|
|
58
|
+
registry.register(*MODELS, *extra)
|
|
59
59
|
manager.provide(SecurityModelAnalysis, registry.freeze())
|
|
60
60
|
function = next(s for s in module.body if isinstance(s, nodes.Function) and s.name == name)
|
|
61
61
|
return manager.get(TaintAnalysis, function)
|
|
@@ -130,6 +130,17 @@ def test_sanitizing_before_the_call_removes_the_flow() -> None:
|
|
|
130
130
|
assert result.flows == ()
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
def test_a_sanitizer_declared_on_a_project_function_overrides_its_summary() -> None:
|
|
134
|
+
source = (
|
|
135
|
+
"import os\n\n"
|
|
136
|
+
"def clean(value):\n return value\n\n"
|
|
137
|
+
"def run():\n os.system(clean(input()))\n"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
assert lines(facts(source)) == [7]
|
|
141
|
+
assert facts(source, "run", Sanitizer(SymbolId("python.ip.clean"), TaintKind.COMMAND)).flows == ()
|
|
142
|
+
|
|
143
|
+
|
|
133
144
|
def test_keyword_arguments_flow_through_summaries() -> None:
|
|
134
145
|
result = facts(
|
|
135
146
|
"import subprocess\n\n"
|
|
@@ -357,6 +357,51 @@ def test_discovers_plugins_recursively_in_sorted_order(tmp_path: Path) -> None:
|
|
|
357
357
|
assert [plugin.manifest.name for plugin in loaded] == ["first", "second"]
|
|
358
358
|
|
|
359
359
|
|
|
360
|
+
def write_package(directory: Path, label: str) -> None:
|
|
361
|
+
package = directory / "sample"
|
|
362
|
+
(package / "tables").mkdir(parents=True)
|
|
363
|
+
(package / "__init__.py").write_text(
|
|
364
|
+
"from typing import ClassVar\n\n"
|
|
365
|
+
"from coretrace_python.plugins import ModelPlugin\n\n"
|
|
366
|
+
"from .tables.celery import MODELS\n\n\n"
|
|
367
|
+
"class SamplePlugin(ModelPlugin):\n"
|
|
368
|
+
' name: ClassVar[str] = "sample"\n'
|
|
369
|
+
" models = MODELS\n",
|
|
370
|
+
encoding="utf-8",
|
|
371
|
+
)
|
|
372
|
+
(package / "tables" / "__init__.py").write_text("", encoding="utf-8")
|
|
373
|
+
(package / "tables" / "celery.py").write_text(
|
|
374
|
+
"from coretrace_python.semantic.symbols import SymbolId\n"
|
|
375
|
+
"from coretrace_python.taint import EntryPoint\n\n"
|
|
376
|
+
f'MODELS = (EntryPoint(SymbolId("python.celery.{label}"), "{label}"),)\n',
|
|
377
|
+
encoding="utf-8",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_loads_a_plugin_package_with_relative_imports(tmp_path: Path) -> None:
|
|
382
|
+
write_manifest(tmp_path)
|
|
383
|
+
write_package(tmp_path, "task")
|
|
384
|
+
|
|
385
|
+
loaded = load_plugin(tmp_path, manager_for(""))
|
|
386
|
+
|
|
387
|
+
assert [m.symbol for m in loaded.plugin.models] == [SymbolId("python.celery.task")]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_plugin_packages_with_the_same_module_name_do_not_collide(tmp_path: Path) -> None:
|
|
391
|
+
for name in ("a", "b"):
|
|
392
|
+
directory = tmp_path / name
|
|
393
|
+
directory.mkdir()
|
|
394
|
+
write_manifest(directory, name=name)
|
|
395
|
+
write_package(directory, name)
|
|
396
|
+
|
|
397
|
+
loaded = discover_plugins(tmp_path, manager_for(""))
|
|
398
|
+
|
|
399
|
+
assert [m.symbol for p in loaded for m in p.plugin.models] == [
|
|
400
|
+
SymbolId("python.celery.a"),
|
|
401
|
+
SymbolId("python.celery.b"),
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
|
|
360
405
|
# --------------------------------------------------------------------------- registry
|
|
361
406
|
|
|
362
407
|
|
|
@@ -136,6 +136,38 @@ def test_taint_flows_into_a_function_defined_in_another_file(tmp_path: Path) ->
|
|
|
136
136
|
assert findings[0].metadata["sink_line"] == "4"
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def test_a_sanitizer_declared_on_a_function_of_another_file_refutes_the_flow(tmp_path: Path) -> None:
|
|
140
|
+
root = project(
|
|
141
|
+
tmp_path / "src",
|
|
142
|
+
{
|
|
143
|
+
"app/__init__.py": "",
|
|
144
|
+
"app/validation.py": "def clean(value):\n return value\n",
|
|
145
|
+
"app/main.py": "import os\nfrom app.validation import clean\n\ndef run():\n os.system(clean(input()))\n",
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
models = project(
|
|
149
|
+
tmp_path / "models",
|
|
150
|
+
{
|
|
151
|
+
"plugin.toml": (
|
|
152
|
+
'name = "house-models"\nversion = "1.0.0"\nplugin_api = ">=1,<2"\n'
|
|
153
|
+
'requires = []\nprovides = ["models.house"]\n\n'
|
|
154
|
+
'[entrypoint]\nmodule = "house"\nclass = "HouseModels"\n'
|
|
155
|
+
),
|
|
156
|
+
"house.py": (
|
|
157
|
+
"from coretrace_python.plugins import ModelPlugin\n"
|
|
158
|
+
"from coretrace_python.semantic.symbols import SymbolId\n"
|
|
159
|
+
"from coretrace_python.taint import Sanitizer, TaintKind\n\n\n"
|
|
160
|
+
"class HouseModels(ModelPlugin):\n"
|
|
161
|
+
' name = "house-models"\n'
|
|
162
|
+
' models = (Sanitizer(SymbolId("python.app.validation.clean"), TaintKind.COMMAND),)\n'
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assert rules(engine.analyze_project(root, [PLUGINS]).findings) == [("main.py", "command-injection", 5)]
|
|
168
|
+
assert rules(engine.analyze_project(root, [PLUGINS, models]).findings) == []
|
|
169
|
+
|
|
170
|
+
|
|
139
171
|
def test_module_attribute_calls_resolve_too(tmp_path: Path) -> None:
|
|
140
172
|
root = project(
|
|
141
173
|
tmp_path,
|
|
@@ -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.3.0"
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_the_changelog_starts_with_the_current_version() -> None:
|
|
@@ -101,6 +101,38 @@ def test_taint_kinds_form_a_bitset() -> None:
|
|
|
101
101
|
assert {TaintKind.SQL, TaintKind.COMMAND, TaintKind.HTML, TaintKind.PATH, TaintKind.SSRF} <= set(TaintKind)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def test_nosql_is_ordinary_input_while_log_and_pii_are_opt_in() -> None:
|
|
105
|
+
assert TaintKind.NOSQL in TaintKind.ALL
|
|
106
|
+
assert TaintKind.LOG not in TaintKind.ALL
|
|
107
|
+
assert TaintKind.PII not in TaintKind.ALL
|
|
108
|
+
assert TaintKind.CREDENTIAL not in TaintKind.ALL
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_ordinary_input_reaches_a_nosql_sink_but_not_a_log_sink() -> None:
|
|
112
|
+
registry = SecurityModelRegistry()
|
|
113
|
+
registry.register(
|
|
114
|
+
Source(sym("python.flask.request.args"), HTTP),
|
|
115
|
+
Source(sym("python.app.record"), "record", TaintKind.LOG),
|
|
116
|
+
Sink(sym("python.db.find"), TaintKind.NOSQL),
|
|
117
|
+
Sink(sym("python.logging.info"), TaintKind.LOG),
|
|
118
|
+
)
|
|
119
|
+
_, facts = analyze(
|
|
120
|
+
"from flask import request\n"
|
|
121
|
+
"import db, logging\n"
|
|
122
|
+
"from app import record\n\n"
|
|
123
|
+
"def run():\n"
|
|
124
|
+
" db.find(request.args['q'])\n"
|
|
125
|
+
" logging.info(request.args['q'])\n"
|
|
126
|
+
" logging.info(record())\n",
|
|
127
|
+
registry.freeze(),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
assert [(f.sink.symbol, f.kinds) for f in facts.flows] == [
|
|
131
|
+
(sym("python.db.find"), TaintKind.NOSQL),
|
|
132
|
+
(sym("python.logging.info"), TaintKind.LOG),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
104
136
|
def test_taint_joins_with_or() -> None:
|
|
105
137
|
a = Taint(TaintKind.SQL, frozenset({Source(sym("python.a"), "a")}))
|
|
106
138
|
b = Taint(TaintKind.HTML, frozenset({Source(sym("python.b"), "b")}))
|
|
@@ -131,10 +163,44 @@ def test_registry_indexes_models_by_symbol() -> None:
|
|
|
131
163
|
|
|
132
164
|
def test_registry_rejects_conflicting_models() -> None:
|
|
133
165
|
registry = SecurityModelRegistry()
|
|
134
|
-
registry.register(
|
|
166
|
+
registry.register(Source(sym("python.flask.request.args"), "http"))
|
|
167
|
+
|
|
168
|
+
with pytest.raises(ModelError, match="python.flask.request.args"):
|
|
169
|
+
registry.register(Source(sym("python.flask.request.args"), "cli"))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_registry_ignores_an_identical_model() -> None:
|
|
173
|
+
registry = SecurityModelRegistry()
|
|
174
|
+
registry.register(Sanitizer(sym("python.html.escape"), TaintKind.HTML))
|
|
175
|
+
registry.register(Sanitizer(sym("python.html.escape"), TaintKind.HTML))
|
|
176
|
+
|
|
177
|
+
assert registry.freeze().sanitizers == (Sanitizer(sym("python.html.escape"), TaintKind.HTML),)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_registry_merges_sinks_on_one_symbol() -> None:
|
|
181
|
+
registry = SecurityModelRegistry()
|
|
182
|
+
registry.register(Sink(sym("python.os.system"), TaintKind.COMMAND, ((TaintKind.COMMAND, (0,)),)))
|
|
183
|
+
registry.register(Sink(sym("python.os.system"), TaintKind.SQL))
|
|
184
|
+
|
|
185
|
+
sink = registry.freeze().sink(sym("python.os.system"))
|
|
186
|
+
assert sink == Sink(
|
|
187
|
+
sym("python.os.system"), TaintKind.COMMAND | TaintKind.SQL, ((TaintKind.COMMAND, (0,)),)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_plugin_models_names_both_plugins_of_a_conflict() -> None:
|
|
192
|
+
from coretrace_python.plugins import ModelPlugin
|
|
193
|
+
|
|
194
|
+
class First(ModelPlugin):
|
|
195
|
+
name = "first-models"
|
|
196
|
+
models = (Source(sym("python.flask.request.args"), "http"),)
|
|
197
|
+
|
|
198
|
+
class Second(ModelPlugin):
|
|
199
|
+
name = "second-models"
|
|
200
|
+
models = (Source(sym("python.flask.request.args"), "cli"),)
|
|
135
201
|
|
|
136
|
-
with pytest.raises(ModelError, match="
|
|
137
|
-
|
|
202
|
+
with pytest.raises(ModelError, match="'first-models' and 'second-models'"):
|
|
203
|
+
engine.plugin_models([First(), Second()])
|
|
138
204
|
|
|
139
205
|
|
|
140
206
|
def test_models_and_tables_are_immutable() -> None:
|
{coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
{coretrace_python_analyzer-0.2.0 → coretrace_python_analyzer-0.3.0}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|