coretrace-python-analyzer 0.7.0__tar.gz → 0.9.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.
Files changed (266) hide show
  1. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/CHANGELOG.md +18 -0
  2. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/plugins.md +19 -2
  4. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/usage.md +19 -2
  5. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cache.py +16 -1
  8. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cli.py +2 -0
  9. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/__init__.py +2 -0
  10. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/advisories.py +36 -1
  11. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/correlation.py +4 -1
  12. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/graph.py +34 -1
  13. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/engine.py +21 -6
  14. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/refutation.py +41 -15
  15. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/__init__.py +8 -0
  16. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/summaries.py +131 -28
  17. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/api.py +8 -0
  18. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/engine.py +35 -28
  19. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/models.py +12 -0
  20. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/healthchecks.json +0 -56
  21. coretrace_python_analyzer-0.9.0/tests/test_attacker_arguments.py +203 -0
  22. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cache.py +3 -0
  23. coretrace_python_analyzer-0.9.0/tests/test_project_models.py +117 -0
  24. coretrace_python_analyzer-0.9.0/tests/test_project_validators.py +93 -0
  25. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_release.py +1 -1
  26. coretrace_python_analyzer-0.9.0/tests/test_summary_sanitizers.py +136 -0
  27. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/ci.yml +0 -0
  28. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.github/workflows/release.yml +0 -0
  29. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/.gitignore +0 -0
  30. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/LICENSE +0 -0
  31. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/NOTICE +0 -0
  32. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/README.md +0 -0
  33. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/docs/architecture.md +0 -0
  34. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/examples/add.py +0 -0
  35. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/examples/canonical_symbols.py +0 -0
  36. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/__main__.py +0 -0
  37. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/__init__.py +0 -0
  38. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/constants.py +0 -0
  39. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/heap.py +0 -0
  40. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/ranges.py +0 -0
  41. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/abstract/values.py +0 -0
  42. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/__init__.py +0 -0
  43. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/manager.py +0 -0
  44. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/analysis/provider.py +0 -0
  45. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  46. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  47. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  48. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  49. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  50. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  51. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  52. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  53. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  54. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  55. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  56. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  57. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  58. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  59. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  60. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  61. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  62. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  63. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  64. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  65. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  66. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  67. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  68. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  70. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  71. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  72. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  73. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  74. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  75. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  76. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  77. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  78. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  79. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  80. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  81. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  82. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  83. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  84. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  85. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  86. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  87. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  88. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  90. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  92. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  93. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  94. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  95. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  97. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  98. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  99. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  100. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  101. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  102. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  103. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  104. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  106. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  107. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  108. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  109. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  110. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  111. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  112. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  113. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  114. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  115. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/__init__.py +0 -0
  116. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/builder.py +0 -0
  117. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/dominance.py +0 -0
  118. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/cfg/model.py +0 -0
  119. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  120. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  121. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dataflow/solver.py +0 -0
  122. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/policy.py +0 -0
  123. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/sbom.py +0 -0
  124. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/dependency/vex.py +0 -0
  125. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/__init__.py +0 -0
  126. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/baseline.py +0 -0
  127. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/coverage.py +0 -0
  128. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/model.py +0 -0
  129. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/findings/suppressions.py +0 -0
  130. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/__init__.py +0 -0
  131. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  132. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/frontend/parser.py +0 -0
  133. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/__init__.py +0 -0
  134. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/nodes.py +0 -0
  135. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/hir/visitors.py +0 -0
  136. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
  137. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  138. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/__init__.py +0 -0
  139. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/defuse.py +0 -0
  140. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/lowering.py +0 -0
  141. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/model.py +0 -0
  142. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/printer.py +0 -0
  143. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/ir/ssa.py +0 -0
  144. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/__init__.py +0 -0
  145. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/detectors.py +0 -0
  146. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/loader.py +0 -0
  147. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/manifest.py +0 -0
  148. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/registry.py +0 -0
  149. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/plugins/secrets.py +0 -0
  150. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/__init__.py +0 -0
  151. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/json_format.py +0 -0
  152. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/report.py +0 -0
  153. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/sarif.py +0 -0
  154. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/reporters/text.py +0 -0
  155. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/__init__.py +0 -0
  156. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/identity.py +0 -0
  157. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/imports.py +0 -0
  158. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/scopes.py +0 -0
  159. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/semantic/symbols.py +0 -0
  160. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/__init__.py +0 -0
  161. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/manager.py +0 -0
  162. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/source/model.py +0 -0
  163. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/__init__.py +0 -0
  164. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/routes.py +0 -0
  165. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/src/coretrace_python/taint/templates.py +0 -0
  166. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Chatbot.json +0 -0
  167. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/DjangoGoat.json +0 -0
  168. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  169. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/InsecureGram.json +0 -0
  170. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  171. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/NPL-IA.json +0 -0
  172. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  173. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  174. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/dvpwa.json +0 -0
  175. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  176. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/httpie.json +0 -0
  177. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/luigi.json +0 -0
  178. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/my_gdb.json +0 -0
  179. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/network-topologer.json +0 -0
  180. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/pygoat.json +0 -0
  181. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  182. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  183. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/expected/yt-fts.json +0 -0
  184. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  185. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  186. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  187. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  188. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/regression/repositories.toml +0 -0
  189. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_evidence.py +0 -0
  190. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_advisory_feeds.py +0 -0
  191. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_aiohttp_models.py +0 -0
  192. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_analysis_manager.py +0 -0
  193. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_architecture.py +0 -0
  194. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_argument_conditions.py +0 -0
  195. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_audit_detectors.py +0 -0
  196. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_baseline.py +0 -0
  197. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cfg.py +0 -0
  198. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_class_keywords_with_targets.py +0 -0
  199. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_cli.py +0 -0
  200. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_closures.py +0 -0
  201. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_config_secrets_coverage.py +0 -0
  202. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_constants.py +0 -0
  203. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_correlation.py +0 -0
  204. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_credential_context.py +0 -0
  205. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dataflow.py +0 -0
  206. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dependencies.py +0 -0
  207. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_detectors.py +0 -0
  208. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_discovery.py +0 -0
  209. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_http_clients.py +0 -0
  210. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_routes.py +0 -0
  211. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_django_safe_values.py +0 -0
  212. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_docs.py +0 -0
  213. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_dominance.py +0 -0
  214. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_emit_ir.py +0 -0
  215. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_exceptions.py +0 -0
  216. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_factories_match_loop_else.py +0 -0
  217. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_fail_on.py +0 -0
  218. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_fastapi_cli_precision.py +0 -0
  219. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_frameworks.py +0 -0
  220. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_frontend_gaps.py +0 -0
  221. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_heap.py +0 -0
  222. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_hex_digests.py +0 -0
  223. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_hir_builder.py +0 -0
  224. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_imports.py +0 -0
  225. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural.py +0 -0
  226. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_interprocedural_taint.py +0 -0
  227. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ir.py +0 -0
  228. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_match_patterns.py +0 -0
  229. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_module_level.py +0 -0
  230. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_nonlocal_writeback.py +0 -0
  231. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_operator_sources.py +0 -0
  232. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_packaging.py +0 -0
  233. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_parallel.py +0 -0
  234. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_parser.py +0 -0
  235. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_plaintext_credentials.py +0 -0
  236. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_plugins.py +0 -0
  237. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_project.py +0 -0
  238. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_pyir_imports.py +0 -0
  239. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_read_entry_points.py +0 -0
  240. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_real_code_precision.py +0 -0
  241. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_redirect_targets.py +0 -0
  242. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation.py +0 -0
  243. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_refutation_evidence.py +0 -0
  244. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_regression.py +0 -0
  245. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_relative_paths.py +0 -0
  246. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_remaining_limits.py +0 -0
  247. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_reporters.py +0 -0
  248. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_safe_arguments.py +0 -0
  249. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_scale.py +0 -0
  250. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_scopes.py +0 -0
  251. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_secrets_sbom.py +0 -0
  252. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_self_state.py +0 -0
  253. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_source_manager.py +0 -0
  254. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssa.py +0 -0
  255. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_ssrf_targets.py +0 -0
  256. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_suppressions.py +0 -0
  257. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbol_resolution.py +0 -0
  258. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_symbols.py +0 -0
  259. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntactic_rules.py +0 -0
  260. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_control_flow.py +0 -0
  261. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_coverage.py +0 -0
  262. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_syntax_expressions.py +0 -0
  263. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_taint.py +0 -0
  264. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_template_escaping.py +0 -0
  265. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_tornado_bottle_models.py +0 -0
  266. {coretrace_python_analyzer-0.7.0 → coretrace_python_analyzer-0.9.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 (2026-09-25)
4
+
5
+ ### Precision
6
+
7
+ - An advisory entry point may name the `attacker_arguments` through which attacker input exploits it: by keyword, and by position when the argument may be passed positionally, a variadic one taking every later position. Attacker input in another argument leaves the call reachable instead of exploitable, so an advisory for `send_from_directory` that names its `path` no longer counts `download_name=`, and one for `requests.get` that names its `url` no longer counts the body. An argument unpacked with `*` or `**` may fill any of them: like an argument condition it leaves undecided, it does not rule the call out, so a wrapper forwarding `*args, **kwargs` still reaches the entry point. An entry point naming no argument counts every argument, as before. Each taint flow now records how its value is passed to the sink (#145).
8
+
9
+ ## 0.8.0 (2026-09-25)
10
+
11
+ ### Precision
12
+
13
+ - A `Validator` proves its argument safe only for the kinds it declares. A validator declared for `REDIRECT` used to refute a command injection through the same value; the flow is now a hotspot, and it is refuted only when the validator covers every kind reaching the sink (#143).
14
+ - A sanitizer called inside a project function protects the flows through that function, for every caller. That covers a helper that escapes before answering, a view redirecting to `reverse(...)`, and a helper rendering a template the analyzer shows escaping. Function summaries keep, for each parameter, the taint kinds cleared on every path from it to an external call or to the return value. A path that skips the sanitizer, or a kind it does not clear, still reaches the sink. The cache format is bumped (#134).
15
+
16
+ ### Plugins
17
+
18
+ - `Plugin.project_models(root)` lets a plugin read models from the project under analysis, such as validators the project declares in a file. It is called once per directory check, in every process, and its models are part of the cache key. A model declared wrongly raises `ModelError`, which the command line now reports as an error (exit status 2) instead of a traceback (#140).
19
+ - A `Validator` model may name a function of the project by its project symbol (`python.hc.accounts.views._allow_redirect`), and the refutation recognises it in its own module too, where a call to it has no imported symbol. A project can thus declare its own validation helpers, and a flow they guard is refuted (#134).
20
+
3
21
  ## 0.7.0 (2026-09-25)
4
22
 
5
23
  ### Precision
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.7.0
3
+ Version: 0.9.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
@@ -142,7 +142,7 @@ class BottleModels(ModelPlugin):
142
142
  |---|---|
143
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. |
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
- | `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`. |
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`. |
147
147
  | `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
148
148
  | `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
@@ -150,7 +150,7 @@ class BottleModels(ModelPlugin):
150
150
  | `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
151
151
  | `SafeArgument(symbol, argument, values, position=None, kinds=ALL)` | A call to the sink `symbol` whose `argument` (by keyword, or at `position`) denotes one of `values` is not a sink for `kinds`, such as `yaml.load` with `Loader=yaml.SafeLoader`. Values are written as the analyzer records arguments: a symbol by its canonical name, a constant as Python writes it (`True`). Only a value given explicitly makes the call safe. |
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
- | `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. |
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
155
 
156
156
  Two plugins may describe the same symbol. An identical model is registered once; two
@@ -158,6 +158,23 @@ Two plugins may describe the same symbol. An identical model is registered once;
158
158
  another plugin declared for `COMMAND`; any other difference is a conflict that stops
159
159
  the analysis with a `ModelError` naming both plugins.
160
160
 
161
+ A plugin may also read models from the project it analyses: `project_models(self, root)`
162
+ returns them, from a file at `root` for instance. It is called once per directory check,
163
+ in every process analysing it, before any module. Its models join the plugin's own and
164
+ are part of what cached results depend on. A single-file check reads no project. A
165
+ model declared wrongly raises `ModelError`, which stops the check with its message
166
+ (exit status 2). This is how a project declares validators of its own:
167
+
168
+ ```python
169
+ class DeclaredValidators(ModelPlugin):
170
+ name: ClassVar[str] = "declared-validators"
171
+
172
+ def project_models(self, root: Path) -> Sequence[Model]:
173
+ path = root / "validators.txt"
174
+ lines = path.read_text(encoding="utf-8").splitlines() if path.is_file() else []
175
+ return tuple(Validator(SymbolId(line)) for line in lines if line)
176
+ ```
177
+
161
178
  A model plugin may also carry `advisories`, a tuple of `Advisory` values
162
179
  (`coretrace_python.dependency`) with the package, the vulnerable range and the affected
163
180
  symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
@@ -101,7 +101,11 @@ confidence (`high`, `medium`, `low`).
101
101
  ### Taint flows
102
102
 
103
103
  Reported when attacker-controlled data reaches a sensitive call without passing through
104
- a sanitizer for that kind of sink.
104
+ a sanitizer for that kind of sink. The sanitizer may also sit inside a project function
105
+ the data goes through, like a helper that escapes before it answers, or a view that
106
+ redirects to `reverse(...)`. It then protects every caller, on every path through the
107
+ function where it is called. What a function stores into an object or a `nonlocal`
108
+ variable is not treated that way yet.
105
109
 
106
110
  | Rule | Reached sink |
107
111
  |---|---|
@@ -235,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
235
239
  ```json
236
240
  {
237
241
  "schema_version": 1,
238
- "tool": {"name": "coretrace-python-analyzer", "version": "0.7.0"},
242
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.9.0"},
239
243
  "root": "/home/me/project",
240
244
  "findings": [
241
245
  {
@@ -332,6 +336,19 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
332
336
  `request.form.get('name')` all read it. A function reading it several times is reported
333
337
  once, where it first reads it.
334
338
 
339
+ Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
340
+ serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
341
+ point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
342
+ passed positionally, by `position`, counted as for argument conditions; `"variadic": true`
343
+ extends a position to every later one, as `safe_join(directory, *pathnames)` takes
344
+ (`{"position": 1, "variadic": true}`). A call is then exploitable only when attacker
345
+ input reaches one of them, `"attacker_arguments": [{"argument": "url", "position": 0}]`
346
+ for `requests.get`; attacker input in another argument leaves it reachable. An argument
347
+ unpacked with `*args` or `**kwargs` may fill any of them: like an argument condition it
348
+ leaves undecided, it does not rule the call out, so it counts, and a wrapper forwarding
349
+ `*args, **kwargs` to the entry point still reaches it. An entry point without
350
+ `attacker_arguments` is exploitable through any argument.
351
+
335
352
  An `argument` condition is decided at each call. It names the argument by keyword and,
336
353
  when it may be passed positionally, by `position` (counted from 0, the receiver of a
337
354
  method excluded); `values` are what makes the call affected, written as the analyzer
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.7.0"
7
+ version = "0.9.0"
8
8
  description = "Static security analysis infrastructure for Python code"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,4 +1,4 @@
1
1
  """CoreTrace's Python static analysis frontend."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.9.0"
4
4
 
@@ -25,6 +25,7 @@ from coretrace_python.findings import Confidence, Finding, Severity
25
25
  from coretrace_python.interprocedural import (
26
26
  Arguments,
27
27
  CallSite,
28
+ Cleared,
28
29
  ExternalCall,
29
30
  ExternalSymbol,
30
31
  FunctionSummary,
@@ -41,7 +42,7 @@ from coretrace_python.interprocedural import (
41
42
  from coretrace_python.semantic.symbols import SymbolId
42
43
  from coretrace_python.source import SourceId, SourceSpan
43
44
 
44
- CACHE_FORMAT = 9
45
+ CACHE_FORMAT = 10
45
46
 
46
47
 
47
48
  @dataclass(frozen=True)
@@ -237,6 +238,8 @@ def _encode_call(call: ExternalCall) -> dict[str, Any]:
237
238
  "location": _encode_span(call.location),
238
239
  "call_site": None if call.call_site is None else _encode_span(call.call_site),
239
240
  "given": _encode_arguments(call.arguments),
241
+ "argument_cleared": [_encode_cleared(c) for c in call.argument_cleared],
242
+ "keyword_cleared": [_encode_cleared(c) for c in call.keyword_cleared],
240
243
  }
241
244
 
242
245
 
@@ -249,9 +252,19 @@ def _decode_call(data: Mapping[str, Any]) -> ExternalCall:
249
252
  _decode_span(data["location"]),
250
253
  None if site is None else _decode_span(site),
251
254
  _decode_arguments(data["given"]),
255
+ tuple(_decode_cleared(c) for c in data["argument_cleared"]),
256
+ tuple(_decode_cleared(c) for c in data["keyword_cleared"]),
252
257
  )
253
258
 
254
259
 
260
+ def _encode_cleared(cleared: Cleared) -> list[list[int]]:
261
+ return [[parameter, bits] for parameter, bits in cleared]
262
+
263
+
264
+ def _decode_cleared(data: Any) -> Cleared:
265
+ return tuple((_integer(parameter), _integer(bits)) for parameter, bits in data)
266
+
267
+
255
268
  def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
256
269
  return {
257
270
  "name": summary.name,
@@ -275,6 +288,7 @@ def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
275
288
  for w in summary.nonlocal_writes
276
289
  ],
277
290
  "static": summary.static,
291
+ "return_cleared": _encode_cleared(summary.return_cleared),
278
292
  }
279
293
 
280
294
 
@@ -303,6 +317,7 @@ def _decode_summary(data: Mapping[str, Any]) -> FunctionSummary:
303
317
  for w in data["nonlocal_writes"]
304
318
  ),
305
319
  bool(data["static"]),
320
+ _decode_cleared(data["return_cleared"]),
306
321
  )
307
322
 
308
323
 
@@ -27,6 +27,7 @@ from coretrace_python.reporters import FORMATS, render
27
27
  from coretrace_python.semantic.imports import ImportResolutionError
28
28
  from coretrace_python.semantic.scopes import ScopeError
29
29
  from coretrace_python.source import SourceManager
30
+ from coretrace_python.taint import ModelError
30
31
 
31
32
  EXIT_CLEAN = 0
32
33
  EXIT_FINDINGS = 1
@@ -45,6 +46,7 @@ _ANALYSIS_ERRORS = (
45
46
  AnalysisError,
46
47
  ManifestError,
47
48
  IncompatiblePluginError,
49
+ ModelError,
48
50
  )
49
51
 
50
52
 
@@ -12,6 +12,7 @@ from coretrace_python.dependency.graph import (
12
12
  DEPENDENCY_FILES,
13
13
  Advisory,
14
14
  AdvisoryEntryPoint,
15
+ AttackerArgument,
15
16
  Condition,
16
17
  DependencyAnalysis,
17
18
  DependencyGraph,
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "Advisory",
32
33
  "AdvisoryEntryPoint",
33
34
  "AdvisoryFileError",
35
+ "AttackerArgument",
34
36
  "Condition",
35
37
  "DependencyAnalysis",
36
38
  "DependencyGraph",
@@ -17,7 +17,13 @@ from collections.abc import Iterable, Iterator, Mapping
17
17
  from pathlib import Path
18
18
  from typing import Any
19
19
 
20
- from coretrace_python.dependency.graph import Advisory, AdvisoryEntryPoint, Condition, normalize
20
+ from coretrace_python.dependency.graph import (
21
+ Advisory,
22
+ AdvisoryEntryPoint,
23
+ AttackerArgument,
24
+ Condition,
25
+ normalize,
26
+ )
21
27
  from coretrace_python.findings import Severity
22
28
  from coretrace_python.semantic.symbols import SymbolId
23
29
 
@@ -155,6 +161,19 @@ def _entry_point_entry(entry_point: AdvisoryEntryPoint) -> dict[str, Any]:
155
161
  }
156
162
  if entry_point.read:
157
163
  entry["read"] = True
164
+ if entry_point.attacker_arguments:
165
+ entry["attacker_arguments"] = [_attacker_argument_entry(a) for a in entry_point.attacker_arguments]
166
+ return entry
167
+
168
+
169
+ def _attacker_argument_entry(argument: AttackerArgument) -> dict[str, Any]:
170
+ entry: dict[str, Any] = {}
171
+ if argument.argument is not None:
172
+ entry["argument"] = argument.argument
173
+ if argument.position is not None:
174
+ entry["position"] = argument.position
175
+ if argument.variadic:
176
+ entry["variadic"] = True
158
177
  return entry
159
178
 
160
179
 
@@ -206,9 +225,25 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
206
225
  str(entry["justification"]),
207
226
  tuple(_condition(c) for c in entry.get("conditions") or []),
208
227
  read,
228
+ tuple(_attacker_argument(a) for a in entry.get("attacker_arguments") or []),
209
229
  )
210
230
 
211
231
 
232
+ def _attacker_argument(entry: Mapping[str, Any]) -> AttackerArgument:
233
+ argument, position, variadic = entry.get("argument"), entry.get("position"), entry.get("variadic", False)
234
+ if argument is not None and not isinstance(argument, str):
235
+ raise TypeError(f"attacker argument name must be a string, got {argument!r}")
236
+ if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
237
+ raise TypeError(f"attacker argument position must be a non-negative integer, got {position!r}")
238
+ if not isinstance(variadic, bool):
239
+ raise TypeError(f"attacker argument variadic must be true or false, got {variadic!r}")
240
+ if argument is None and position is None:
241
+ raise ValueError("an attacker argument needs a name, a position or both")
242
+ if variadic and position is None:
243
+ raise ValueError("a variadic attacker argument needs the position it starts at")
244
+ return AttackerArgument(argument, position, variadic)
245
+
246
+
212
247
  def _condition(entry: Mapping[str, Any]) -> Condition:
213
248
  position = entry.get("position")
214
249
  if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
@@ -133,7 +133,10 @@ def correlate(
133
133
  continue
134
134
  hotspot = verdict is not None and verdict.status is Status.HOTSPOT
135
135
  for advisory in affected.get(flow.sink.symbol, ()):
136
- check = check_conditions(advisory.entry_point(flow.sink.symbol), flow.sink_arguments)
136
+ entry = advisory.entry_point(flow.sink.symbol)
137
+ if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
138
+ continue
139
+ check = check_conditions(entry, flow.sink_arguments)
137
140
  if check.contradicted is None:
138
141
  findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
139
142
  return tuple(findings)
@@ -166,6 +166,31 @@ class Condition:
166
166
  return self.kind == "argument"
167
167
 
168
168
 
169
+ @dataclass(frozen=True)
170
+ class AttackerArgument:
171
+ """An argument of an entry point that must carry the attacker's input: passed by
172
+ keyword ``argument``, or at ``position`` (from 0, a method's receiver excluded) when it
173
+ may be passed positionally; a ``variadic`` one, such as ``*pathnames``, also takes every
174
+ later position."""
175
+
176
+ argument: str | None = None
177
+ position: int | None = None
178
+ variadic: bool = False
179
+
180
+ def receives(self, position: int | None, keyword: str | None) -> bool:
181
+ """Whether the argument passed at ``position`` or as ``keyword`` is this one. An
182
+ argument unpacked with ``*`` or ``**`` has neither and may fill this one: like an
183
+ argument condition it leaves undecided, it does not rule the call out, so it is."""
184
+
185
+ if keyword is not None:
186
+ return keyword == self.argument
187
+ if position is None:
188
+ return True
189
+ if self.position is None:
190
+ return False
191
+ return position == self.position or (self.variadic and position > self.position)
192
+
193
+
169
194
  @dataclass(frozen=True)
170
195
  class AdvisoryEntryPoint:
171
196
  """A public API through which a project reaches an affected symbol, justified by the
@@ -173,12 +198,20 @@ class AdvisoryEntryPoint:
173
198
 
174
199
  An entry point is reached by a call, or — when ``read`` is set, for an attribute whose
175
200
  getter runs the affected code, such as a lazily parsed request body — by any read of
176
- it or of an attribute of it."""
201
+ it or of an attribute of it. When it names ``attacker_arguments``, attacker input
202
+ exploits it only through one of them; otherwise through any argument."""
177
203
 
178
204
  symbol: SymbolId
179
205
  justification: str
180
206
  conditions: tuple[Condition, ...] = ()
181
207
  read: bool = False
208
+ attacker_arguments: tuple[AttackerArgument, ...] = ()
209
+
210
+ def exploitable_through(self, position: int | None, keyword: str | None) -> bool:
211
+ """Whether attacker input in the argument at ``position`` or ``keyword`` can
212
+ exploit the vulnerability."""
213
+
214
+ return not self.attacker_arguments or any(a.receives(position, keyword) for a in self.attacker_arguments)
182
215
 
183
216
 
184
217
  DIRECT = "affected symbol, changed by the fix"
@@ -70,6 +70,7 @@ from coretrace_python.interprocedural import (
70
70
  CallGraph,
71
71
  CallGraphAnalysis,
72
72
  CallSite,
73
+ ClearingAnalysis,
73
74
  FunctionSummary,
74
75
  ModuleFunction,
75
76
  ModuleGraph,
@@ -149,6 +150,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
149
150
  DependencyAnalysis,
150
151
  RegisteredRoutes,
151
152
  EscapedTemplates,
153
+ ClearingAnalysis,
152
154
  )
153
155
 
154
156
 
@@ -188,6 +190,7 @@ class ResultsEvicted(TransformationPass):
188
190
  DependencyAnalysis,
189
191
  RegisteredRoutes,
190
192
  EscapedTemplates,
193
+ ClearingAnalysis,
191
194
  }
192
195
  )
193
196
 
@@ -246,7 +249,9 @@ def build_manager(
246
249
  """A manager with every engine analysis registered and the engine inputs provided."""
247
250
 
248
251
  manager = _register_all(module)
249
- manager.provide(SecurityModelAnalysis, (models or SecurityModelRegistry()).freeze())
252
+ table = (models or SecurityModelRegistry()).freeze()
253
+ manager.provide(SecurityModelAnalysis, table)
254
+ manager.provide(ClearingAnalysis, table.clearing())
250
255
  manager.provide(ProjectSummaries, SummaryIndex())
251
256
  manager.provide(RegisteredRoutes, _routes_of(manager))
252
257
  return manager
@@ -276,10 +281,14 @@ def load_plugins(plugin_roots: Sequence[Path], manager: AnalysisManager) -> Plug
276
281
  return registry
277
282
 
278
283
 
279
- def plugin_models(plugins: Iterable[Plugin]) -> ModelTable:
284
+ def plugin_models(plugins: Iterable[Plugin], root: Path | None = None) -> ModelTable:
285
+ """The models of ``plugins``, with those they read from the project at ``root``."""
286
+
280
287
  models = SecurityModelRegistry()
281
288
  for plugin in plugins:
282
289
  models.register(*plugin.models, origin=plugin.name)
290
+ if root is not None:
291
+ models.register(*plugin.project_models(root), origin=plugin.name)
283
292
  return models.freeze()
284
293
 
285
294
 
@@ -294,7 +303,9 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
294
303
 
295
304
  manager = _register_all(build_hir(source))
296
305
  registry = load_plugins(plugin_roots, manager)
297
- manager.provide(SecurityModelAnalysis, plugin_models(loaded.plugin for loaded in registry))
306
+ table = plugin_models(loaded.plugin for loaded in registry)
307
+ manager.provide(SecurityModelAnalysis, table)
308
+ manager.provide(ClearingAnalysis, table.clearing())
298
309
  manager.provide(ProjectSummaries, SummaryIndex())
299
310
  manager.provide(RegisteredRoutes, _routes_of(manager))
300
311
  findings, supported = _check_module(manager, tuple(loaded.plugin for loaded in registry))
@@ -385,7 +396,7 @@ def analyze_project(
385
396
  (a for plugin in all_plugins for a in plugin.advisories), file_advisories
386
397
  )
387
398
  affected = affected_symbols(dependencies, advisories)
388
- models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
399
+ models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
389
400
  for manager in managers.values():
390
401
  manager.provide(SecurityModelAnalysis, models)
391
402
  manager.provide(DependencyAnalysis, dependencies)
@@ -408,9 +419,11 @@ def analyze_project(
408
419
  for symbol, registered in _routes_of(analysable[name]).items():
409
420
  routes.setdefault(symbol, registered)
410
421
  escaped = escaped_templates(root)
422
+ clearing = models.clearing(escaped)
411
423
  for manager in analysable.values():
412
424
  manager.provide(RegisteredRoutes, routes)
413
425
  manager.provide(EscapedTemplates, escaped)
426
+ manager.provide(ClearingAnalysis, clearing)
414
427
 
415
428
  configuration = _configuration_key(registry, plugins, models, advisories, dependencies, routes, escaped)
416
429
  keys = module_keys(
@@ -620,13 +633,15 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
620
633
  (a for plugin in all_plugins for a in plugin.advisories), file_advisories
621
634
  )
622
635
  affected = affected_symbols(dependencies, advisories)
623
- models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
636
+ models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
624
637
  routes = _decode_routes(batch.routes)
638
+ escaped = frozenset(batch.escaped)
625
639
  for manager in managers.values():
626
640
  manager.provide(SecurityModelAnalysis, models)
627
641
  manager.provide(DependencyAnalysis, dependencies)
628
642
  manager.provide(RegisteredRoutes, routes)
629
- manager.provide(EscapedTemplates, frozenset(batch.escaped))
643
+ manager.provide(EscapedTemplates, escaped)
644
+ manager.provide(ClearingAnalysis, models.clearing(escaped))
630
645
  module_plugins = tuple(p for p in all_plugins if not isinstance(p, ProjectPlugin))
631
646
  results = _analyse_managers(managers, decode_index(batch.seed), module_plugins, affected)
632
647
  return {name: encode(entry) for name, entry in results.items()}
@@ -29,7 +29,12 @@ from coretrace_python.abstract import ConstantPropagation, RangeAnalysis, RangeF
29
29
  from coretrace_python.analysis import AnalysisContext, AnyAnalysis, FunctionAnalysis
30
30
  from coretrace_python.cfg import CFG, BlockId, CFGAnalysis, DominanceAnalysis, DominatorTree
31
31
  from coretrace_python.hir import nodes
32
- from coretrace_python.interprocedural import CallGraphAnalysis
32
+ from coretrace_python.interprocedural import (
33
+ CallGraph,
34
+ CallGraphAnalysis,
35
+ KnownFunction,
36
+ project_symbol,
37
+ )
33
38
  from coretrace_python.ir.model import (
34
39
  BoolOp,
35
40
  Branch,
@@ -55,6 +60,7 @@ from coretrace_python.taint import (
55
60
  TaintAnalysis,
56
61
  TaintFacts,
57
62
  TaintFlow,
63
+ TaintKind,
58
64
  )
59
65
 
60
66
  VALIDATORS = frozenset(
@@ -214,15 +220,16 @@ class _Judge:
214
220
 
215
221
  def interpret(
216
222
  self, condition: Value, truth: bool | None
217
- ) -> tuple[dict[Value, str], set[Value]]:
218
- """Values the condition proves safe (with the reason), and values it mentions
219
- without proving anything. ``truth`` is ``None`` when it is not fixed at the sink.
223
+ ) -> tuple[dict[Value, tuple[str, TaintKind]], set[Value]]:
224
+ """Values the condition proves safe (with the reason and the kinds it proves them
225
+ safe for), and values it mentions without proving anything. ``truth`` is ``None``
226
+ when it is not fixed at the sink.
220
227
 
221
228
  A recognised check evaluated the wrong way (``isdigit()`` known false) yields
222
229
  nothing: it is neither a proof nor a reassuring guard."""
223
230
 
224
231
  definition = self.defs.get(condition)
225
- proven: dict[Value, str] = {}
232
+ proven: dict[Value, tuple[str, TaintKind]] = {}
226
233
  mentioned: set[Value] = set()
227
234
  if isinstance(definition, UnaryOp) and definition.operator == "not":
228
235
  return self.interpret(definition.operand, None if truth is None else not truth)
@@ -236,27 +243,29 @@ class _Judge:
236
243
  recognised = self.recognise(definition)
237
244
  if recognised is None:
238
245
  return proven, set(self.closure(condition)) | {condition}
239
- tested, reason, when_true = recognised
246
+ tested, reason, when_true, kinds = recognised
240
247
  if truth is None:
241
248
  mentioned = set(self.closure(condition)) | {condition}
242
249
  elif truth == when_true:
243
250
  # The check proves ``tested``; whatever else it reads is merely mentioned.
244
- proven[tested] = reason
251
+ proven[tested] = (reason, kinds)
245
252
  mentioned = set(self.closure(condition)) | {condition}
246
253
  return proven, mentioned
247
254
 
248
- def recognise(self, definition: Instruction | None) -> tuple[Value, str, bool] | None:
249
- """``(validated value, reason, truth that validates)`` for known check shapes."""
255
+ def recognise(self, definition: Instruction | None) -> tuple[Value, str, bool, TaintKind] | None:
256
+ """``(validated value, reason, truth that validates, kinds it proves)`` for known
257
+ check shapes. A string check, a constant allowlist or an equality with a constant
258
+ proves every kind; a ``Validator`` only the kinds it declares."""
250
259
 
251
260
  if isinstance(definition, Call) and not definition.arguments:
252
261
  callee = self.defs.get(definition.callee)
253
262
  if isinstance(callee, GetAttr) and callee.attribute in VALIDATORS:
254
- return callee.object, f"guarded by {callee.attribute}()", True
263
+ return callee.object, f"guarded by {callee.attribute}()", True, TaintKind.ALL
255
264
  if isinstance(definition, Call):
256
265
  symbol = self.symbols.get(definition.callee)
257
266
  validator = self.models.validator(symbol) if symbol is not None else None
258
267
  if validator is not None and validator.argument < len(definition.arguments):
259
- return definition.arguments[validator.argument], f"validated by {symbol}", True
268
+ return definition.arguments[validator.argument], f"validated by {symbol}", True, validator.kinds
260
269
  if isinstance(definition, Compare):
261
270
  left, right = definition.left, definition.right
262
271
  if definition.operator in ("in", "not_in") and self.is_constant_collection(right):
@@ -264,11 +273,12 @@ class _Judge:
264
273
  left,
265
274
  "allowlisted by a membership check on constants",
266
275
  definition.operator == "in",
276
+ TaintKind.ALL,
267
277
  )
268
278
  if definition.operator in ("eq", "not_eq"):
269
279
  for tested, other in ((left, right), (right, left)):
270
280
  if isinstance(self.defs.get(other), Constant):
271
- return tested, "equals a constant", definition.operator == "eq"
281
+ return tested, "equals a constant", definition.operator == "eq", TaintKind.ALL
272
282
  return None
273
283
 
274
284
  def examined(
@@ -387,8 +397,9 @@ class _Judge:
387
397
  )
388
398
  for guard in self.guards(sink):
389
399
  found, mentioned = self.interpret(guard.condition, guard.truth)
390
- for value, reason in found.items():
391
- if value in chain:
400
+ for value, (reason, kinds) in found.items():
401
+ # A check proving some kinds only leaves the others reaching the sink.
402
+ if value in chain and not flow.kinds & ~kinds:
392
403
  proven.setdefault(value, reason)
393
404
  examined = self.examined(guard.condition, mentioned, flow.argument, chain)
394
405
  for origin in origins:
@@ -478,6 +489,7 @@ class RefutationAnalysis(FunctionAnalysis[Verdicts]):
478
489
  constants = ctx.get(ConstantPropagation, function)
479
490
  graph = ctx.get(CallGraphAnalysis)
480
491
  models = ctx.get(SecurityModelAnalysis)
492
+ name = graph.name_of(function)
481
493
  return judge_flows(
482
494
  ssa,
483
495
  ctx.get(CFGAnalysis, function),
@@ -486,7 +498,21 @@ class RefutationAnalysis(FunctionAnalysis[Verdicts]):
486
498
  frozenset(b.id for b in ssa.blocks if constants.reachable(b.id)),
487
499
  ctx.get(RangeAnalysis, function),
488
500
  models,
489
- graph.symbols(graph.name_of(function)),
501
+ _with_project_callees(ssa, graph, name, ctx.module.name),
490
502
  authorization_of(function, models, ctx.get(ScopeAnalysis), ctx.get(SymbolAnalysis)),
491
503
  )
492
504
 
505
+
506
+ def _with_project_callees(function: FunctionIR, graph: CallGraph, name: str, module: str) -> dict[Value, SymbolId]:
507
+ """The symbols of ``name``'s values, with each call to a function of its module named
508
+ by its project symbol, so a model declared on it (``Validator``) applies there too."""
509
+
510
+ symbols = dict(graph.symbols(name))
511
+ for block in function.blocks:
512
+ for instruction in block.instructions:
513
+ if isinstance(instruction, Call):
514
+ target = graph.target_at(name, instruction.location)
515
+ if isinstance(target, KnownFunction):
516
+ symbols.setdefault(instruction.callee, project_symbol(module, target.name))
517
+ return symbols
518
+
@@ -20,6 +20,9 @@ from coretrace_python.interprocedural.modulegraph import (
20
20
  project_symbol,
21
21
  )
22
22
  from coretrace_python.interprocedural.summaries import (
23
+ Cleared,
24
+ Clearing,
25
+ ClearingAnalysis,
23
26
  ExternalCall,
24
27
  FunctionSummary,
25
28
  Mutation,
@@ -28,6 +31,7 @@ from coretrace_python.interprocedural.summaries import (
28
31
  SummaryAnalysis,
29
32
  SummaryIndex,
30
33
  SummaryTable,
34
+ cleared_by,
31
35
  )
32
36
 
33
37
  __all__ = [
@@ -35,6 +39,9 @@ __all__ = [
35
39
  "CallGraph",
36
40
  "CallGraphAnalysis",
37
41
  "CallSite",
42
+ "Cleared",
43
+ "Clearing",
44
+ "ClearingAnalysis",
38
45
  "ExternalCall",
39
46
  "ExternalSymbol",
40
47
  "FunctionSummary",
@@ -51,6 +58,7 @@ __all__ = [
51
58
  "Target",
52
59
  "UnknownTarget",
53
60
  "build_module_graph",
61
+ "cleared_by",
54
62
  "discover_files",
55
63
  "discover_sources",
56
64
  "project_symbol",