coretrace-python-analyzer 0.15.0__tar.gz → 0.17.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 (286) hide show
  1. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/CHANGELOG.md +14 -0
  2. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/plugins.md +6 -1
  4. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/usage.md +64 -4
  5. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +9 -2
  8. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/django/django_models.py +4 -1
  9. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/vex.py +10 -1
  10. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/engine.py +34 -3
  11. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/coverage.py +4 -1
  12. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/__init__.py +12 -0
  13. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/callgraph.py +52 -7
  14. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/summaries.py +179 -7
  15. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/api.py +7 -4
  16. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/__init__.py +18 -1
  17. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/engine.py +97 -11
  18. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/models.py +62 -2
  19. coretrace_python_analyzer-0.17.0/src/coretrace_python/taint/templates.py +690 -0
  20. coretrace_python_analyzer-0.17.0/tests/test_base_patterns.py +326 -0
  21. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_release.py +1 -1
  22. coretrace_python_analyzer-0.17.0/tests/test_template_context_flows.py +306 -0
  23. coretrace_python_analyzer-0.17.0/tests/test_template_filter_calls.py +283 -0
  24. coretrace_python_analyzer-0.17.0/tests/test_template_request_processor.py +215 -0
  25. coretrace_python_analyzer-0.15.0/src/coretrace_python/taint/templates.py +0 -250
  26. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.github/workflows/ci.yml +0 -0
  27. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.github/workflows/release.yml +0 -0
  28. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/.gitignore +0 -0
  29. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/LICENSE +0 -0
  30. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/NOTICE +0 -0
  31. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/README.md +0 -0
  32. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/docs/architecture.md +0 -0
  33. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/examples/add.py +0 -0
  34. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/examples/canonical_symbols.py +0 -0
  35. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/__main__.py +0 -0
  36. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/__init__.py +0 -0
  37. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/constants.py +0 -0
  38. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/heap.py +0 -0
  39. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/ranges.py +0 -0
  40. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/strings.py +0 -0
  41. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/abstract/values.py +0 -0
  42. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/__init__.py +0 -0
  43. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/manager.py +0 -0
  44. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/analysis/provider.py +0 -0
  45. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  46. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  47. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  48. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  49. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  50. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  51. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  52. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  53. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  54. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  55. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  56. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  57. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  59. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  60. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  61. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  62. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  63. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  64. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  65. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  66. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  67. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  68. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  70. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  71. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  72. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  73. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  74. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  75. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  76. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  77. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  78. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  79. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
  80. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
  81. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  82. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  83. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  84. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  85. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
  86. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
  87. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  88. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  90. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  92. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  94. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  95. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  97. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  99. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  100. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  101. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  102. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  103. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  104. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  106. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  107. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  108. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  109. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  110. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  111. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  112. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  113. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  114. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  115. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  116. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  117. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cache.py +0 -0
  118. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/__init__.py +0 -0
  119. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/builder.py +0 -0
  120. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/dominance.py +0 -0
  121. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cfg/model.py +0 -0
  122. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/cli.py +0 -0
  123. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  124. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  125. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dataflow/solver.py +0 -0
  126. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/__init__.py +0 -0
  127. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/advisories.py +0 -0
  128. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/correlation.py +0 -0
  129. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/graph.py +0 -0
  130. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/policy.py +0 -0
  131. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/dependency/sbom.py +0 -0
  132. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/__init__.py +0 -0
  133. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/baseline.py +0 -0
  134. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/model.py +0 -0
  135. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/provenance.py +0 -0
  136. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/refutation.py +0 -0
  137. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/findings/suppressions.py +0 -0
  138. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/__init__.py +0 -0
  139. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  140. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/frontend/parser.py +0 -0
  141. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/__init__.py +0 -0
  142. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/nodes.py +0 -0
  143. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/hir/visitors.py +0 -0
  144. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  145. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/__init__.py +0 -0
  146. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/defuse.py +0 -0
  147. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/lowering.py +0 -0
  148. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/model.py +0 -0
  149. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/printer.py +0 -0
  150. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/ir/ssa.py +0 -0
  151. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/__init__.py +0 -0
  152. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/detectors.py +0 -0
  153. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/loader.py +0 -0
  154. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/manifest.py +0 -0
  155. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/registry.py +0 -0
  156. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/plugins/secrets.py +0 -0
  157. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/__init__.py +0 -0
  158. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/json_format.py +0 -0
  159. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/report.py +0 -0
  160. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/sarif.py +0 -0
  161. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/reporters/text.py +0 -0
  162. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/__init__.py +0 -0
  163. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/identity.py +0 -0
  164. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/imports.py +0 -0
  165. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/scopes.py +0 -0
  166. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/semantic/symbols.py +0 -0
  167. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/__init__.py +0 -0
  168. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/manager.py +0 -0
  169. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/source/model.py +0 -0
  170. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/routes.py +0 -0
  171. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/src/coretrace_python/taint/urls.py +0 -0
  172. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Chatbot.json +0 -0
  173. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/DjangoGoat.json +0 -0
  174. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  175. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/InsecureGram.json +0 -0
  176. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  177. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/NPL-IA.json +0 -0
  178. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  179. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  180. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/dvpwa.json +0 -0
  181. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  182. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/healthchecks.json +0 -0
  183. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/httpie.json +0 -0
  184. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/luigi.json +0 -0
  185. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/my_gdb.json +0 -0
  186. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/network-topologer.json +0 -0
  187. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/pygoat.json +0 -0
  188. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  189. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  190. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/expected/yt-fts.json +0 -0
  191. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  192. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  193. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  194. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  195. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/regression/repositories.toml +0 -0
  196. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_evidence.py +0 -0
  197. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_feeds.py +0 -0
  198. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_advisory_url_host.py +0 -0
  199. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_aiohttp_models.py +0 -0
  200. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_analysis_manager.py +0 -0
  201. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_architecture.py +0 -0
  202. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_argument_conditions.py +0 -0
  203. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_attacker_arguments.py +0 -0
  204. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_audit_detectors.py +0 -0
  205. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_baseline.py +0 -0
  206. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cache.py +0 -0
  207. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cfg.py +0 -0
  208. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_class_keywords_with_targets.py +0 -0
  209. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_cli.py +0 -0
  210. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_closures.py +0 -0
  211. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_code_injection.py +0 -0
  212. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_config_secrets_coverage.py +0 -0
  213. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_constants.py +0 -0
  214. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_correlation.py +0 -0
  215. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_credential_context.py +0 -0
  216. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dataflow.py +0 -0
  217. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dependencies.py +0 -0
  218. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_detectors.py +0 -0
  219. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_discovery.py +0 -0
  220. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_http_clients.py +0 -0
  221. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_routes.py +0 -0
  222. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_django_safe_values.py +0 -0
  223. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_docs.py +0 -0
  224. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_dominance.py +0 -0
  225. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_emit_ir.py +0 -0
  226. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_exceptions.py +0 -0
  227. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_factories_match_loop_else.py +0 -0
  228. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_fail_on.py +0 -0
  229. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_fastapi_cli_precision.py +0 -0
  230. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_frameworks.py +0 -0
  231. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_frontend_gaps.py +0 -0
  232. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_heap.py +0 -0
  233. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_hex_digests.py +0 -0
  234. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_hir_builder.py +0 -0
  235. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_imports.py +0 -0
  236. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_interprocedural.py +0 -0
  237. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_interprocedural_taint.py +0 -0
  238. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ir.py +0 -0
  239. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_match_patterns.py +0 -0
  240. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_members.py +0 -0
  241. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_module_level.py +0 -0
  242. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_nonlocal_writeback.py +0 -0
  243. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_nosql_injection.py +0 -0
  244. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_open_redirect_destinations.py +0 -0
  245. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_operator_sources.py +0 -0
  246. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_packaging.py +0 -0
  247. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_parallel.py +0 -0
  248. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_parser.py +0 -0
  249. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_plaintext_credentials.py +0 -0
  250. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_plugins.py +0 -0
  251. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project.py +0 -0
  252. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project_models.py +0 -0
  253. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_project_validators.py +0 -0
  254. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_provenance.py +0 -0
  255. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_pyir_imports.py +0 -0
  256. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_read_entry_points.py +0 -0
  257. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_real_code_precision.py +0 -0
  258. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_redirect_targets.py +0 -0
  259. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_refutation.py +0 -0
  260. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_refutation_evidence.py +0 -0
  261. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_regression.py +0 -0
  262. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_relative_paths.py +0 -0
  263. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_remaining_limits.py +0 -0
  264. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_reporters.py +0 -0
  265. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_safe_arguments.py +0 -0
  266. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_scale.py +0 -0
  267. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_scopes.py +0 -0
  268. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_secrets_sbom.py +0 -0
  269. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_self_state.py +0 -0
  270. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_source_manager.py +0 -0
  271. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssa.py +0 -0
  272. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssrf_destinations.py +0 -0
  273. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_ssrf_targets.py +0 -0
  274. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_summary_sanitizers.py +0 -0
  275. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_suppressions.py +0 -0
  276. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_symbol_resolution.py +0 -0
  277. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_symbols.py +0 -0
  278. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntactic_rules.py +0 -0
  279. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_control_flow.py +0 -0
  280. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_coverage.py +0 -0
  281. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_syntax_expressions.py +0 -0
  282. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_taint.py +0 -0
  283. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_template_escaping.py +0 -0
  284. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_tornado_bottle_models.py +0 -0
  285. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_url_proof.py +0 -0
  286. {coretrace_python_analyzer-0.15.0 → coretrace_python_analyzer-0.17.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 (2026-09-28)
4
+
5
+ ### Plugins
6
+
7
+ - `BasePattern(pattern, label, parameters, kinds)` makes the methods of a class whose base's canonical symbol matches a regular expression entry points, for bases generated per project such as a gRPC servicer's, whatever the module's prefix and however the base is imported. Each parameter after `self` is, by position, either input of the model's kinds or an object of a class the model names, which the sources on that class taint without the object being input: a `Source` on `ServicerContext.invocation_metadata` gives the client's metadata, and `peer()` gives nothing. When the project holds the base, only the methods the base defines are entry points, so a servicer's utility methods are not; otherwise every method with the listed number of parameters is. `async` methods and the messages of an iterated parameter are covered (#176).
8
+
9
+ ## 0.16.0 (2026-09-27)
10
+
11
+ ### Advisories
12
+
13
+ - A filter of Django's own libraries that a project template applies is a call to the function behind it, at the template's line: `{{ bio|striptags }}` calls `django.template.defaultfilters.striptags`, and so do filters in tag arguments and `{% filter %}` blocks. An advisory entry point naming that function is reachable there, whether or not a Python call names the template, since a class-based view renders its `template_name` inside Django. A template the analyzer cannot read may apply any filter: when the project names one by an expression, or by a name found under no `templates` directory, in `render`, `render_to_string`, `TemplateResponse`, `{% include %}` or `{% extends %}`, an advisory whose entry points include a template filter stays `under_investigation` in the VEX document, and the notes say where. `render`, `TemplateResponse` and `SimpleTemplateResponse` are now template renders like `render_to_string` (#146).
14
+ - Attacker input in a render context reaching a template filter makes an advisory entry point naming that filter exploitable at the render call. The analyzer links them only where it is certain of the value: a dict literal with constant keys that the render call alone uses, and a filter the rendered template applies to a context variable, directly or through `{% for %}`, `{% with %}`, constant `{% include %}` and `{% extends %}`, earlier filter arguments and `{% filter %}` blocks, in the blocks that render. A name a tag binds shadows the context, and `{% include ... only %}` passes none of it. The filter's value is its first argument, the filter's own argument its second. A project function rendering what it receives carries it the same way (#146).
15
+ - The request the `request` context processor adds reaches a template's filters too, as `{{ request.GET.q|striptags }}`, only where the analyzer is certain of it: every Django template engine the settings assign to `TEMPLATES`, as literals no other code names, lists the processor; the render call passes the request (`render`, `TemplateResponse`, `render_to_string` given `request`) with a certain context, absent or a dict literal; nothing shadows `request`; and the template reads a text attribute of the request object, such as `GET`, `POST`, `COOKIES` or `headers`. Otherwise the filter stays reachable (#146).
16
+
3
17
  ## 0.15.0 (2026-09-26)
4
18
 
5
19
  ### Precision
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.15.0
3
+ Version: 0.17.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
@@ -154,10 +154,11 @@ class BottleModels(ModelPlugin):
154
154
  | `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
155
155
  | `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
156
156
  | `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
157
+ | `BasePattern(pattern, label, parameters=(None,), kinds=ALL)` | A regular expression on the canonical symbol of a class's base, for bases generated per project such as a gRPC servicer's (`(?:^|\.)\w+_pb2_grpc\.\w+Servicer$`), whatever the module's prefix and however the base is imported. The methods taking the parameters listed, by position after `self`, are entry points: a position left `None` is `label` input carrying `kinds`; one holding a class symbol denotes an object of that class, which the sources on the class taint, as `invocation_metadata()` on a `ServicerContext`, without the object itself being input. When the project holds the base, only the methods the base defines count; otherwise every method with that number of parameters. Inheritance identifies a potential service, not its registration on a server. |
157
158
  | `RouteRegistrar(symbol, argument, label, kinds=ALL, keyword=None)` | A call registering a view at argument `argument` (or `keyword`), such as Django's `path`; the view receives `label` input. |
158
159
  | `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
159
160
  | `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. |
160
- | `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). |
161
+ | `TemplateRender(symbol, position=0, keyword="template_name", context=1, request=None)` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string`, with the variables of its argument at `context` or `context=` and the request at `request` or `request=` (None: the call takes none); it makes the filter calls the template applies, fed from that context and, with the `request` context processor, that request (see the usage guide). 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). A name the analyzer cannot read keeps an advisory a template filter reaches under investigation in the VEX document. |
161
162
  | `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. |
162
163
  | `RequestObject(symbol, text=())` | The inputs from `symbol`, an entry point, a route registrar, a typed parameter or a source, are request objects. A view receives the request first (after `self`), then URL parameters, which are text. Reading one of the request's `text` attributes gives text, as Django's `GET` or aiohttp's `query`; its body and files keep the request's kinds. An attribute read counts only when every source of the value is such a request. |
163
164
  | `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
@@ -258,6 +259,10 @@ one, and the label of the entry point it is (`http` for a route, `argv` for a co
258
259
  when the models make it one. `ctx.call_graph(module)` gives each function's call sites
259
260
  (`sites(function)`) and the symbols it reads, called or not (`reads(function)`: reading
260
261
  `request.form['name']` reads `python.flask.request.form`), each where it first reads it.
262
+ `ctx.templates` gives the templates found under the project's `templates` directories:
263
+ their `names`, the filters they apply as `calls` (each a `FilterCall(symbol, span)`, the
264
+ function behind the filter and where the template applies it), and where they name a
265
+ template the analyzer cannot read (`unread`).
261
266
  The shipped `vulnerable-dependency`, `reachable-vulnerability` and `dependency-policy`
262
267
  plugins are of this kind.
263
268
 
@@ -241,7 +241,7 @@ the directory of the checked file; a path outside it is printed as it is.
241
241
  ```json
242
242
  {
243
243
  "schema_version": 1,
244
- "tool": {"name": "coretrace-python-analyzer", "version": "0.15.0"},
244
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.17.0"},
245
245
  "root": "/home/me/project",
246
246
  "findings": [
247
247
  {
@@ -273,7 +273,7 @@ bytes. A single-file check lists no components.
273
273
 
274
274
  ```json
275
275
  "tool": {
276
- "name": "coretrace-python-analyzer", "version": "0.15.0",
276
+ "name": "coretrace-python-analyzer", "version": "0.17.0",
277
277
  "components": [
278
278
  {"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
279
279
  {"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
@@ -362,6 +362,57 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
362
362
  `request.form.get('name')` all read it. A function reading it several times is reported
363
363
  once, where it first reads it.
364
364
 
365
+ A filter of Django's own libraries that a project template applies is a call to the
366
+ function behind it, placed at the template's line: `{{ bio|striptags }}` calls
367
+ `python.django.template.defaultfilters.striptags`, and so do filters in tag arguments
368
+ (`{% if bio|striptags %}`) and `{% filter %}` blocks. A template counts whether or not a
369
+ Python call names it, since a class-based view renders its `template_name` inside
370
+ Django. Most filters are functions of the same name in `django.template.defaultfilters`;
371
+ `escape`, `escapejs`, `linebreaks`, `phone2numeric`, `slice`, `timesince` and `timeuntil`
372
+ are `<name>_filter` there, and the filters of `i18n`, `l10n`, `tz` and `humanize` are in
373
+ their library's module (`timezone` is `django.templatetags.tz.do_timezone`).
374
+
375
+ Attacker input in the render context makes the filter exploitable at the render call:
376
+ `render(request, "app/profile.html", {"bio": request.POST["bio"]})` with
377
+ `{{ bio|striptags }}`. The analyzer links a context value to a filter only where it is
378
+ certain of it:
379
+
380
+ - The context is a dict literal with constant keys, used by the render call and
381
+ nothing else, written in the call or through a variable. A dict mutated or passed
382
+ elsewhere, one unpacking another (`**extra`), or a context built any other way leaves
383
+ the filter reachable.
384
+ - The filter's value is a context variable, directly or through `{% for %}` and
385
+ `{% with %}`, the templates the template includes or extends by a constant name, the
386
+ arguments of the filters before it, or the output of a `{% filter %}` block. A name a
387
+ tag binds shadows the context, and `{% include ... only %}` passes none of it.
388
+ - The filter renders: a template extending another renders only its blocks, which
389
+ replace the blocks of that name in what it extends.
390
+ - One file has the template's name; of two files of the same name, which one renders
391
+ depends on the loaders.
392
+
393
+ The filter's value is its first argument and its argument (`cut:bio`) its second, so an
394
+ entry point naming only the value in `attacker_arguments` is not exploitable through
395
+ the argument. A project function rendering the values it receives carries them the same
396
+ way.
397
+
398
+ With the `django.template.context_processors.request` context processor, a template
399
+ rendered with the request reads it as `request`: `{{ request.GET.q|striptags }}` is
400
+ exploitable at the render call too. The analyzer counts it only where all of these hold:
401
+
402
+ - The project's settings assign `TEMPLATES` a literal list, every `DjangoTemplates`
403
+ engine of it lists the processor in a literal `context_processors`, and no other code
404
+ of the project names `TEMPLATES`. A mere mention of the processor proves nothing.
405
+ - The render call passes the request: `render` and `TemplateResponse` always do,
406
+ `render_to_string` when given `request`, `SimpleTemplateResponse` never.
407
+ - The render's context is certain, absent or a dict literal as above, so it cannot hide
408
+ a `request` entry. An entry of that name, a name a tag binds or `{% include ... only %}`
409
+ shadows the processor's request.
410
+ - The template reads text the user controls: an attribute the request object lists
411
+ among its text attributes, such as `GET`, `POST`, `COOKIES` or `headers`; not `user` or
412
+ `session`.
413
+
414
+ Through a project function, only the context entries are followed.
415
+
365
416
  Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
366
417
  serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
367
418
  point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
@@ -444,16 +495,25 @@ check, including findings the policy accepts or a comment suppresses:
444
495
  | Status | When |
445
496
  |---|---|
446
497
  | `affected` | The project's code reaches the vulnerable code. The notes give each place and its level, `reachable` or `exploitable`. |
447
- | `not_affected` | Justified as `vulnerable_code_not_in_execute_path`, only when all of these hold: the advisory names entry points, no code of the project reaches them, every file and function was analysed, and a lock file (`uv.lock` or `poetry.lock`) shows that no other package requires the vulnerable one. The impact statement names the entry points and any calls ruled out by their arguments. |
498
+ | `not_affected` | Justified as `vulnerable_code_not_in_execute_path`, only when all of these hold: the advisory names entry points, no code of the project reaches them, every file and function was analysed, every template the project names was read if a template filter is among them, and a lock file (`uv.lock` or `poetry.lock`) shows that no other package requires the vulnerable one. The impact statement names the entry points and any calls ruled out by their arguments. |
448
499
  | `under_investigation` | Anything else. The notes say which condition failed. |
449
500
 
501
+ A template the analyzer cannot read may apply any filter. The project names one when
502
+ `render`, `render_to_string`, `TemplateResponse`, an `{% include %}` or an `{% extends %}`
503
+ names a template by an expression, or by a name found under no `templates` directory,
504
+ including one an installed package ships; a template file that cannot be read counts
505
+ too. An advisory whose entry points include a template filter then stays
506
+ `under_investigation`, and the notes list where the project names each such template
507
+ (`app.views:12 names a template by an expression`). A template named elsewhere, such as
508
+ a class-based view's `template_name`, is not checked.
509
+
450
510
  `not_affected` needs the lock-file check because the analyzer reads the project's code,
451
511
  not the code of installed packages. A framework calling the vulnerable function on the
452
512
  project's behalf would go unseen. Without a lock file, a package is never
453
513
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
454
514
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
455
515
  cannot know it. Its `tooling` names the analyzer and each component with its digest
456
- (`coretrace-python-analyzer 0.15.0; curated-advisories 2026.09.25 (sha256:…)`), so the
516
+ (`coretrace-python-analyzer 0.17.0; curated-advisories 2026.09.25 (sha256:…)`), so the
457
517
  `@id` changes with the advisory data a statement was decided with.
458
518
 
459
519
  ## Large projects
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.15.0"
7
+ version = "0.17.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.15.0"
3
+ __version__ = "0.17.0"
4
4
 
@@ -1,5 +1,6 @@
1
1
  """Calls, anywhere in the project, to an API affected by a vulnerable requirement, and
2
- reads of an affected attribute whose getter runs the vulnerable code."""
2
+ reads of an affected attribute whose getter runs the vulnerable code. A filter a project
3
+ template applies is a call to the function behind it, at the template's line."""
3
4
 
4
5
  from __future__ import annotations
5
6
 
@@ -56,6 +57,10 @@ class ReachableVulnerabilityPlugin(ProjectPlugin):
56
57
  reported.add((advisory, symbol))
57
58
  check = check_conditions(entry, None)
58
59
  findings.append(_reached(advisory, symbol, read.location, function, check))
60
+ for call in ctx.templates.calls:
61
+ for advisory in affected.get(call.symbol, ()):
62
+ check = check_conditions(advisory.entry_point(call.symbol), None)
63
+ findings.append(_reached(advisory, call.symbol, call.span, None, check))
59
64
  return findings
60
65
 
61
66
 
@@ -67,7 +72,9 @@ def _read_through(symbol: SymbolId) -> Iterator[SymbolId]:
67
72
  yield SymbolId(".".join(components[:end]))
68
73
 
69
74
 
70
- def _reached(advisory: Advisory, symbol: SymbolId, span: SourceSpan, function: str, check: ConditionCheck) -> Finding:
75
+ def _reached(
76
+ advisory: Advisory, symbol: SymbolId, span: SourceSpan, function: str | None, check: ConditionCheck
77
+ ) -> Finding:
71
78
  return Finding(
72
79
  rule_id="reachable-vulnerability",
73
80
  message=(
@@ -139,7 +139,10 @@ class DjangoModels(ModelPlugin):
139
139
  Sink(_sym("django.http.HttpResponseRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
140
140
  Sink(_sym("django.http.HttpResponsePermanentRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
141
141
  Sanitizer(_sym("django.utils.html.escape"), TaintKind.HTML),
142
- TemplateRender(_sym("django.template.loader.render_to_string")),
142
+ TemplateRender(_sym("django.template.loader.render_to_string"), request=2),
143
+ TemplateRender(_sym("django.shortcuts.render"), 1, context=2, request=0),
144
+ TemplateRender(_sym("django.template.response.TemplateResponse"), 1, "template", 2, 0),
145
+ TemplateRender(_sym("django.template.response.SimpleTemplateResponse"), 0, "template"),
143
146
  Sanitizer(_sym("django.utils.html.conditional_escape"), TaintKind.HTML),
144
147
  # The masked CSRF secret: ASCII letters and digits only, a malformed cookie is
145
148
  # replaced before it is used.
@@ -7,7 +7,8 @@ subcomponent of the project:
7
7
  exploitable finding, whatever the policy or a suppression did with it;
8
8
  - ``not_affected``, as ``vulnerable_code_not_in_execute_path``, only when the advisory is
9
9
  curated (it names entry points), no code of the project reaches them, every file and
10
- function was analysed, and a lock file shows that no other package requires the
10
+ function was analysed, every template the project names was read when a template
11
+ filter can reach them, and a lock file shows that no other package requires the
11
12
  vulnerable one, since the code of installed packages is not analysed;
12
13
  - ``under_investigation`` otherwise, with the reason in its notes.
13
14
  """
@@ -25,6 +26,7 @@ from coretrace_python.dependency.graph import Advisory, DependencyGraph, Require
25
26
  from coretrace_python.dependency.sbom import purl
26
27
  from coretrace_python.findings import Component, Finding
27
28
  from coretrace_python.findings.coverage import Coverage
29
+ from coretrace_python.taint import FILTER_FUNCTIONS
28
30
 
29
31
  CONTEXT = "https://openvex.dev/ns/v0.2.0"
30
32
  # OpenVEX's shared namespace for documents without an IRI of their own, and the author
@@ -33,6 +35,8 @@ NAMESPACE = "https://openvex.dev/docs/public/vex-"
33
35
  AUTHOR = "Unknown Author"
34
36
  # Evidence levels that put the vulnerable code in the project's execution path, lowest first.
35
37
  REACHED = ("reachable", "exploitable")
38
+ # What a template calls: a template the engine cannot read may reach these.
39
+ _TEMPLATE_CALLS = frozenset(FILTER_FUNCTIONS.values())
36
40
 
37
41
 
38
42
  def render_vex(
@@ -140,6 +144,11 @@ def _status(
140
144
  f"No analysed code reaches {entries}, but {', '.join(partial)} could not be fully "
141
145
  f"analysed.{ruled}"
142
146
  )
147
+ if coverage.unread_templates and _TEMPLATE_CALLS.intersection(advisory.reachable_symbols):
148
+ return _investigating(
149
+ f"No analysed code reaches {entries}, but a template can reach them, and the engine could "
150
+ f"not read every template the project names: {'; '.join(coverage.unread_templates)}.{ruled}"
151
+ )
143
152
  required_by = dependencies.required_by(package)
144
153
  if required_by is None:
145
154
  return _investigating(
@@ -80,9 +80,12 @@ from coretrace_python.interprocedural import (
80
80
  ModuleFunction,
81
81
  ModuleGraph,
82
82
  ProjectSummaries,
83
+ SignaturesAnalysis,
83
84
  SummaryAnalysis,
84
85
  SummaryIndex,
85
86
  SymbolRead,
87
+ TemplateCalls,
88
+ TemplateCallsAnalysis,
86
89
  build_module_graph,
87
90
  discover_sources,
88
91
  project_symbol,
@@ -124,7 +127,10 @@ from coretrace_python.taint import (
124
127
  TaintAnalysis,
125
128
  TaintKind,
126
129
  escaped_templates,
130
+ project_templates,
127
131
  registered_routes,
132
+ request_processor,
133
+ unread_renders,
128
134
  )
129
135
  from coretrace_python.taint.urls import flow_url
130
136
 
@@ -158,6 +164,8 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
158
164
  RegisteredRoutes,
159
165
  EscapedTemplates,
160
166
  ClearingAnalysis,
167
+ TemplateCallsAnalysis,
168
+ SignaturesAnalysis,
161
169
  )
162
170
 
163
171
 
@@ -198,6 +206,8 @@ class ResultsEvicted(TransformationPass):
198
206
  RegisteredRoutes,
199
207
  EscapedTemplates,
200
208
  ClearingAnalysis,
209
+ TemplateCallsAnalysis,
210
+ SignaturesAnalysis,
201
211
  }
202
212
  )
203
213
 
@@ -261,6 +271,7 @@ def build_manager(
261
271
  table = (models or SecurityModelRegistry()).freeze()
262
272
  manager.provide(SecurityModelAnalysis, table)
263
273
  manager.provide(MembersAnalysis, table.members_by_class())
274
+ manager.provide(SignaturesAnalysis, table.signatures())
264
275
  manager.provide(ClearingAnalysis, table.clearing())
265
276
  manager.provide(ProjectSummaries, SummaryIndex())
266
277
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -316,6 +327,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
316
327
  table = plugin_models(loaded.plugin for loaded in registry)
317
328
  manager.provide(SecurityModelAnalysis, table)
318
329
  manager.provide(MembersAnalysis, table.members_by_class())
330
+ manager.provide(SignaturesAnalysis, table.signatures())
319
331
  manager.provide(ClearingAnalysis, table.clearing())
320
332
  manager.provide(ProjectSummaries, SummaryIndex())
321
333
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -411,6 +423,7 @@ def analyze_project(
411
423
  for manager in managers.values():
412
424
  manager.provide(SecurityModelAnalysis, models)
413
425
  manager.provide(MembersAnalysis, members)
426
+ manager.provide(SignaturesAnalysis, models.signatures())
414
427
  manager.provide(DependencyAnalysis, dependencies)
415
428
 
416
429
  imports: dict[str, ImportTable] = {}
@@ -431,13 +444,20 @@ def analyze_project(
431
444
  for symbol, registered in _routes_of(analysable[name]).items():
432
445
  routes.setdefault(symbol, registered)
433
446
  escaped = escaped_templates(root)
447
+ templates = project_templates(root)
434
448
  clearing = models.clearing(escaped)
449
+ template_calls = models.template_calls(
450
+ templates.filters, request_processor(modules[name] for name in sorted(analysable))
451
+ )
435
452
  for manager in analysable.values():
436
453
  manager.provide(RegisteredRoutes, routes)
437
454
  manager.provide(EscapedTemplates, escaped)
438
455
  manager.provide(ClearingAnalysis, clearing)
456
+ manager.provide(TemplateCallsAnalysis, template_calls)
439
457
 
440
- configuration = _configuration_key(components, plugins, models, advisories, dependencies, routes, escaped)
458
+ configuration = _configuration_key(
459
+ components, plugins, models, advisories, dependencies, routes, escaped, template_calls
460
+ )
441
461
  keys = module_keys(
442
462
  graph,
443
463
  {name: fingerprint(configuration, str(files[name].source_id), name, files[name].text) for name in analysable},
@@ -479,6 +499,7 @@ def analyze_project(
479
499
  advisory_paths,
480
500
  _encode_routes(routes),
481
501
  tuple(sorted(escaped)),
502
+ template_calls,
482
503
  ),
483
504
  )
484
505
  for component in pending
@@ -516,7 +537,9 @@ def analyze_project(
516
537
  frozenset(),
517
538
  reads={f: tuple(r) for f, r in reads.items()},
518
539
  )
519
- context = ProjectContext(graph, dependencies, advisories, analysable, call_graphs, policy, root, functions)
540
+ context = ProjectContext(
541
+ graph, dependencies, advisories, analysable, call_graphs, policy, root, functions, templates
542
+ )
520
543
  for plugin in all_plugins:
521
544
  if isinstance(plugin, ProjectPlugin):
522
545
  findings.extend(plugin.analyze_project(context))
@@ -526,6 +549,9 @@ def analyze_project(
526
549
  findings = [_sourced(finding, origins) for finding in findings]
527
550
  accepted = tuple(f for f in findings if policy.accepts(f))
528
551
  kept, suppressed = partition(apply_policy(policy, findings), _text_of(sources))
552
+ unread = list(templates.unread)
553
+ for name in sorted(call_graphs):
554
+ unread.extend(unread_renders(name, call_graphs[name], models, templates.names))
529
555
  return ProjectAnalysis(
530
556
  graph,
531
557
  index,
@@ -534,7 +560,7 @@ def analyze_project(
534
560
  MappingProxyType(keys),
535
561
  reused,
536
562
  advisories,
537
- Coverage(tuple(sorted(coverage, key=lambda c: c.path))),
563
+ Coverage(tuple(sorted(coverage, key=lambda c: c.path)), tuple(unread)),
538
564
  suppressed,
539
565
  accepted,
540
566
  components,
@@ -686,6 +712,7 @@ class _Batch:
686
712
  advisory_paths: tuple[Path, ...] = ()
687
713
  routes: tuple[tuple[str, str, str, int], ...] = ()
688
714
  escaped: tuple[str, ...] = ()
715
+ template_calls: TemplateCalls = field(default_factory=TemplateCalls)
689
716
 
690
717
 
691
718
  def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
@@ -709,10 +736,12 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
709
736
  for manager in managers.values():
710
737
  manager.provide(SecurityModelAnalysis, models)
711
738
  manager.provide(MembersAnalysis, members)
739
+ manager.provide(SignaturesAnalysis, models.signatures())
712
740
  manager.provide(DependencyAnalysis, dependencies)
713
741
  manager.provide(RegisteredRoutes, routes)
714
742
  manager.provide(EscapedTemplates, escaped)
715
743
  manager.provide(ClearingAnalysis, models.clearing(escaped))
744
+ manager.provide(TemplateCallsAnalysis, batch.template_calls)
716
745
  module_plugins = tuple(p for p in all_plugins if not isinstance(p, ProjectPlugin))
717
746
  results = _analyse_managers(managers, decode_index(batch.seed), module_plugins, affected)
718
747
  return {name: encode(entry) for name, entry in results.items()}
@@ -741,6 +770,7 @@ def _configuration_key(
741
770
  dependencies: DependencyGraph,
742
771
  routes: Routes | None = None,
743
772
  escaped: frozenset[str] = frozenset(),
773
+ template_calls: TemplateCalls | None = None,
744
774
  ) -> str:
745
775
  """Everything a module's results depend on besides the project sources (§11)."""
746
776
 
@@ -758,6 +788,7 @@ def _configuration_key(
758
788
  repr(dependencies.errors),
759
789
  repr(_encode_routes(routes or {})),
760
790
  repr(sorted(escaped)),
791
+ repr(template_calls),
761
792
  )
762
793
 
763
794
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  "No findings" only means something when the reader knows what was analysed. Each file
4
4
  is ``analysed``, a ``syntax-error`` (the frontend rejected it) or ``unreadable`` (it could
5
- not be decoded); analysed files count their functions and how many lowered.
5
+ not be decoded); analysed files count their functions and how many lowered. The
6
+ templates the project names but the run could not read are listed, each where the
7
+ project names it: they may call any template filter.
6
8
  """
7
9
 
8
10
  from __future__ import annotations
@@ -21,6 +23,7 @@ class FileCoverage:
21
23
  @dataclass(frozen=True)
22
24
  class Coverage:
23
25
  details: tuple[FileCoverage, ...] = ()
26
+ unread_templates: tuple[str, ...] = ()
24
27
 
25
28
  @property
26
29
  def files(self) -> int:
@@ -8,6 +8,8 @@ from coretrace_python.interprocedural.callgraph import (
8
8
  ExternalSymbol,
9
9
  KnownFunction,
10
10
  ModuleFunction,
11
+ Signature,
12
+ SignaturesAnalysis,
11
13
  SymbolRead,
12
14
  Target,
13
15
  UnknownTarget,
@@ -20,6 +22,7 @@ from coretrace_python.interprocedural.modulegraph import (
20
22
  project_symbol,
21
23
  )
22
24
  from coretrace_python.interprocedural.summaries import (
25
+ FILTER_ARGUMENTS,
23
26
  Cleared,
24
27
  Clearing,
25
28
  ClearingAnalysis,
@@ -31,10 +34,14 @@ from coretrace_python.interprocedural.summaries import (
31
34
  SummaryAnalysis,
32
35
  SummaryIndex,
33
36
  SummaryTable,
37
+ TemplateCalls,
38
+ TemplateCallsAnalysis,
39
+ TemplateFilter,
34
40
  cleared_by,
35
41
  )
36
42
 
37
43
  __all__ = [
44
+ "FILTER_ARGUMENTS",
38
45
  "Arguments",
39
46
  "CallGraph",
40
47
  "CallGraphAnalysis",
@@ -51,11 +58,16 @@ __all__ = [
51
58
  "Mutation",
52
59
  "NonlocalWrite",
53
60
  "ProjectSummaries",
61
+ "Signature",
62
+ "SignaturesAnalysis",
54
63
  "SummaryAnalysis",
55
64
  "SummaryIndex",
56
65
  "SummaryTable",
57
66
  "SymbolRead",
58
67
  "Target",
68
+ "TemplateCalls",
69
+ "TemplateCallsAnalysis",
70
+ "TemplateFilter",
59
71
  "UnknownTarget",
60
72
  "build_module_graph",
61
73
  "cleared_by",
@@ -9,6 +9,7 @@ reads, called or not: reading ``request.form`` runs its getter.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import re
12
13
  from collections.abc import Mapping
13
14
  from dataclasses import dataclass, field
14
15
  from types import MappingProxyType
@@ -104,6 +105,30 @@ class SymbolRead:
104
105
  symbol: SymbolId
105
106
 
106
107
 
108
+ @dataclass(frozen=True)
109
+ class Signature:
110
+ """What the parameters of the methods of a class denote when the canonical symbol
111
+ of its base matches ``pattern``, by position after ``self``: a class symbol, or None
112
+ for a parameter denoting none. A method taking another number of parameters has
113
+ none. The engine derives signatures from the security models (§16)."""
114
+
115
+ pattern: str
116
+ parameters: tuple[SymbolId | None, ...]
117
+
118
+ def matches(self, base: SymbolId) -> bool:
119
+ return re.search(self.pattern, base.canonical_name) is not None
120
+
121
+
122
+ class SignaturesAnalysis(Analysis[tuple[Signature, ...]]):
123
+ """The signatures the models declare, provided by the engine; none on its own."""
124
+
125
+ name: ClassVar[str] = "interprocedural.signatures"
126
+
127
+ @classmethod
128
+ def compute(cls, ctx: AnalysisContext) -> tuple[Signature, ...]:
129
+ return ()
130
+
131
+
107
132
  @dataclass(frozen=True)
108
133
  class ModuleFunction:
109
134
  """One function of a module as a project plugin sees it: the name the call graph
@@ -357,6 +382,20 @@ def _annotated(
357
382
  return found
358
383
 
359
384
 
385
+ def _declared(
386
+ function: nodes.Function, ssa: FunctionIR, bases: tuple[SymbolId, ...], signatures: tuple[Signature, ...]
387
+ ) -> dict[Value, SymbolId]:
388
+ """Parameters a signature types by their position, for a method of a class one of
389
+ whose ``bases`` matches the signature and which takes the parameters it lists."""
390
+
391
+ given = function.parameters[1:]
392
+ for signature in signatures:
393
+ if any(signature.matches(base) for base in bases) and len(given) == len(signature.parameters):
394
+ typed = zip(ssa.parameters[1:], signature.parameters, strict=False)
395
+ return {value: symbol for value, symbol in typed if symbol is not None}
396
+ return {}
397
+
398
+
360
399
  def _typed_with_module_classes(
361
400
  function: nodes.Function, ssa: FunctionIR, classes: Mapping[str, frozenset[str]]
362
401
  ) -> dict[Value, str]:
@@ -372,12 +411,15 @@ def _typed_with_module_classes(
372
411
 
373
412
  class CallGraphAnalysis(Analysis[CallGraph]):
374
413
  name: ClassVar[str] = "interprocedural.callgraph"
375
- requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, ScopeAnalysis, SymbolAnalysis})
414
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
415
+ {SSAAnalysis, ScopeAnalysis, SymbolAnalysis, SignaturesAnalysis}
416
+ )
376
417
 
377
418
  @classmethod
378
419
  def compute(cls, ctx: AnalysisContext) -> CallGraph:
379
420
  scopes = ctx.get(ScopeAnalysis)
380
421
  table = ctx.get(SymbolAnalysis)
422
+ signatures = ctx.get(SignaturesAnalysis)
381
423
  definitions: dict[str, nodes.Function] = {}
382
424
  for function in analyzable_functions(ctx.module):
383
425
  # A property and its setter, or a redefinition, share a qualified name; each
@@ -396,14 +438,15 @@ class CallGraphAnalysis(Analysis[CallGraph]):
396
438
  for statement in ctx.module.body
397
439
  if isinstance(statement, nodes.Class)
398
440
  }
441
+ # Inheritance follows the first base that resolves; a signature may match any.
399
442
  bases: dict[str, SymbolId] = {}
443
+ ancestry: dict[str, tuple[SymbolId, ...]] = {}
400
444
  for statement in ctx.module.body:
401
445
  if isinstance(statement, nodes.Class):
402
- for base_expression in statement.bases:
403
- symbol = table.resolve_expression(scopes.module_scope.id, base_expression)
404
- if symbol is not None:
405
- bases[statement.name] = symbol
406
- break
446
+ resolved = [table.resolve_expression(scopes.module_scope.id, base) for base in statement.bases]
447
+ ancestry[statement.name] = tuple(symbol for symbol in resolved if symbol is not None)
448
+ if ancestry[statement.name]:
449
+ bases[statement.name] = ancestry[statement.name][0]
407
450
  sites: dict[str, tuple[CallSite, ...]] = {}
408
451
  symbols: dict[str, Mapping[Value, SymbolId]] = {}
409
452
  reads: dict[str, tuple[SymbolRead, ...]] = {}
@@ -418,11 +461,13 @@ class CallGraphAnalysis(Analysis[CallGraph]):
418
461
  owner = name.rsplit(".", 1)[0] if "." in name and name.rsplit(".", 1)[0] in classes else None
419
462
  if owner is not None and _is_staticmethod(function):
420
463
  owner = None # a static method has no receiver
464
+ # A signature types parameters by their position; an annotation says more.
465
+ declared = _declared(function, ssa, ancestry.get(owner, ()) if owner is not None else (), signatures)
421
466
  targets, symbols[name] = resolve_targets(
422
467
  ssa,
423
468
  scopes,
424
469
  known,
425
- _annotated(function, ssa, scopes, table),
470
+ {**declared, **_annotated(function, ssa, scopes, table)},
426
471
  frozenset(definitions),
427
472
  classes,
428
473
  owner,