coretrace-python-analyzer 0.14.0__tar.gz → 0.16.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 (287) hide show
  1. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/CHANGELOG.md +18 -0
  2. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/docs/plugins.md +8 -2
  4. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/docs/usage.md +82 -5
  5. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +9 -2
  8. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/django/django_models.py +4 -1
  9. coretrace_python_analyzer-0.16.0/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +47 -0
  10. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +1 -1
  11. coretrace_python_analyzer-0.16.0/src/coretrace_python/bundled/security/ssrf/ssrf.py +49 -0
  12. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/advisories.py +6 -1
  13. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/correlation.py +36 -5
  14. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/graph.py +4 -2
  15. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/vex.py +10 -1
  16. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/engine.py +34 -9
  17. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/coverage.py +4 -1
  18. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/interprocedural/__init__.py +8 -0
  19. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/interprocedural/summaries.py +173 -7
  20. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/api.py +7 -4
  21. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/taint/__init__.py +16 -1
  22. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/taint/engine.py +63 -4
  23. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/taint/models.py +20 -2
  24. coretrace_python_analyzer-0.16.0/src/coretrace_python/taint/templates.py +690 -0
  25. coretrace_python_analyzer-0.16.0/src/coretrace_python/taint/urls.py +70 -0
  26. coretrace_python_analyzer-0.16.0/tests/test_advisory_url_host.py +145 -0
  27. coretrace_python_analyzer-0.16.0/tests/test_open_redirect_destinations.py +116 -0
  28. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_release.py +1 -1
  29. coretrace_python_analyzer-0.16.0/tests/test_template_context_flows.py +306 -0
  30. coretrace_python_analyzer-0.16.0/tests/test_template_filter_calls.py +283 -0
  31. coretrace_python_analyzer-0.16.0/tests/test_template_request_processor.py +215 -0
  32. coretrace_python_analyzer-0.16.0/tests/test_url_proof.py +80 -0
  33. coretrace_python_analyzer-0.14.0/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -17
  34. coretrace_python_analyzer-0.14.0/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -64
  35. coretrace_python_analyzer-0.14.0/src/coretrace_python/taint/templates.py +0 -250
  36. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/.github/workflows/ci.yml +0 -0
  37. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/.github/workflows/release.yml +0 -0
  38. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/.gitignore +0 -0
  39. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/LICENSE +0 -0
  40. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/NOTICE +0 -0
  41. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/README.md +0 -0
  42. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/docs/architecture.md +0 -0
  43. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/examples/add.py +0 -0
  44. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/examples/canonical_symbols.py +0 -0
  45. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/__main__.py +0 -0
  46. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/__init__.py +0 -0
  47. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/constants.py +0 -0
  48. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/heap.py +0 -0
  49. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/ranges.py +0 -0
  50. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/strings.py +0 -0
  51. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/abstract/values.py +0 -0
  52. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/analysis/__init__.py +0 -0
  53. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/analysis/manager.py +0 -0
  54. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/analysis/provider.py +0 -0
  55. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  56. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  57. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  59. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  60. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  61. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  62. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  63. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  64. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  65. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  66. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  67. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  68. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  69. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  70. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  71. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  72. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  73. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  74. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  75. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  76. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  77. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  78. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  79. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  80. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  81. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  83. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  84. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  85. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  86. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  87. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  88. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
  90. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  92. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  94. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  95. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
  96. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
  97. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  98. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  99. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  100. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  101. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  103. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  104. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  106. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  107. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  108. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  109. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  110. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  111. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  112. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  113. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  114. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  115. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  116. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  117. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  118. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  119. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  120. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  121. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  122. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  123. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  124. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cache.py +0 -0
  125. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cfg/__init__.py +0 -0
  126. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cfg/builder.py +0 -0
  127. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cfg/dominance.py +0 -0
  128. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cfg/model.py +0 -0
  129. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/cli.py +0 -0
  130. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  131. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  132. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dataflow/solver.py +0 -0
  133. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/__init__.py +0 -0
  134. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/policy.py +0 -0
  135. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/dependency/sbom.py +0 -0
  136. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/__init__.py +0 -0
  137. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/baseline.py +0 -0
  138. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/model.py +0 -0
  139. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/provenance.py +0 -0
  140. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/refutation.py +0 -0
  141. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/findings/suppressions.py +0 -0
  142. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/frontend/__init__.py +0 -0
  143. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  144. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/frontend/parser.py +0 -0
  145. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/hir/__init__.py +0 -0
  146. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/hir/nodes.py +0 -0
  147. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/hir/visitors.py +0 -0
  148. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
  149. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  150. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/__init__.py +0 -0
  151. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/defuse.py +0 -0
  152. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/lowering.py +0 -0
  153. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/model.py +0 -0
  154. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/printer.py +0 -0
  155. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/ir/ssa.py +0 -0
  156. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/__init__.py +0 -0
  157. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/detectors.py +0 -0
  158. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/loader.py +0 -0
  159. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/manifest.py +0 -0
  160. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/registry.py +0 -0
  161. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/plugins/secrets.py +0 -0
  162. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/reporters/__init__.py +0 -0
  163. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/reporters/json_format.py +0 -0
  164. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/reporters/report.py +0 -0
  165. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/reporters/sarif.py +0 -0
  166. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/reporters/text.py +0 -0
  167. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/semantic/__init__.py +0 -0
  168. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/semantic/identity.py +0 -0
  169. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/semantic/imports.py +0 -0
  170. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/semantic/scopes.py +0 -0
  171. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/semantic/symbols.py +0 -0
  172. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/source/__init__.py +0 -0
  173. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/source/manager.py +0 -0
  174. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/source/model.py +0 -0
  175. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/src/coretrace_python/taint/routes.py +0 -0
  176. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/Chatbot.json +0 -0
  177. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/DjangoGoat.json +0 -0
  178. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  179. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/InsecureGram.json +0 -0
  180. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  181. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/NPL-IA.json +0 -0
  182. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  183. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  184. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/dvpwa.json +0 -0
  185. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  186. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/healthchecks.json +0 -0
  187. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/httpie.json +0 -0
  188. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/luigi.json +0 -0
  189. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/my_gdb.json +0 -0
  190. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/network-topologer.json +0 -0
  191. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/pygoat.json +0 -0
  192. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  193. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  194. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/expected/yt-fts.json +0 -0
  195. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  196. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  197. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  198. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  199. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/regression/repositories.toml +0 -0
  200. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_advisory_evidence.py +0 -0
  201. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_advisory_feeds.py +0 -0
  202. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_aiohttp_models.py +0 -0
  203. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_analysis_manager.py +0 -0
  204. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_architecture.py +0 -0
  205. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_argument_conditions.py +0 -0
  206. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_attacker_arguments.py +0 -0
  207. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_audit_detectors.py +0 -0
  208. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_baseline.py +0 -0
  209. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_cache.py +0 -0
  210. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_cfg.py +0 -0
  211. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_class_keywords_with_targets.py +0 -0
  212. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_cli.py +0 -0
  213. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_closures.py +0 -0
  214. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_code_injection.py +0 -0
  215. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_config_secrets_coverage.py +0 -0
  216. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_constants.py +0 -0
  217. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_correlation.py +0 -0
  218. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_credential_context.py +0 -0
  219. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_dataflow.py +0 -0
  220. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_dependencies.py +0 -0
  221. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_detectors.py +0 -0
  222. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_discovery.py +0 -0
  223. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_django_http_clients.py +0 -0
  224. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_django_routes.py +0 -0
  225. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_django_safe_values.py +0 -0
  226. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_docs.py +0 -0
  227. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_dominance.py +0 -0
  228. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_emit_ir.py +0 -0
  229. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_exceptions.py +0 -0
  230. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_factories_match_loop_else.py +0 -0
  231. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_fail_on.py +0 -0
  232. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_fastapi_cli_precision.py +0 -0
  233. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_frameworks.py +0 -0
  234. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_frontend_gaps.py +0 -0
  235. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_heap.py +0 -0
  236. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_hex_digests.py +0 -0
  237. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_hir_builder.py +0 -0
  238. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_imports.py +0 -0
  239. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_interprocedural.py +0 -0
  240. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_interprocedural_taint.py +0 -0
  241. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_ir.py +0 -0
  242. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_match_patterns.py +0 -0
  243. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_members.py +0 -0
  244. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_module_level.py +0 -0
  245. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_nonlocal_writeback.py +0 -0
  246. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_nosql_injection.py +0 -0
  247. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_operator_sources.py +0 -0
  248. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_packaging.py +0 -0
  249. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_parallel.py +0 -0
  250. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_parser.py +0 -0
  251. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_plaintext_credentials.py +0 -0
  252. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_plugins.py +0 -0
  253. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_project.py +0 -0
  254. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_project_models.py +0 -0
  255. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_project_validators.py +0 -0
  256. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_provenance.py +0 -0
  257. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_pyir_imports.py +0 -0
  258. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_read_entry_points.py +0 -0
  259. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_real_code_precision.py +0 -0
  260. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_redirect_targets.py +0 -0
  261. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_refutation.py +0 -0
  262. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_refutation_evidence.py +0 -0
  263. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_regression.py +0 -0
  264. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_relative_paths.py +0 -0
  265. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_remaining_limits.py +0 -0
  266. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_reporters.py +0 -0
  267. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_safe_arguments.py +0 -0
  268. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_scale.py +0 -0
  269. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_scopes.py +0 -0
  270. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_secrets_sbom.py +0 -0
  271. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_self_state.py +0 -0
  272. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_source_manager.py +0 -0
  273. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_ssa.py +0 -0
  274. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_ssrf_destinations.py +0 -0
  275. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_ssrf_targets.py +0 -0
  276. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_summary_sanitizers.py +0 -0
  277. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_suppressions.py +0 -0
  278. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_symbol_resolution.py +0 -0
  279. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_symbols.py +0 -0
  280. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_syntactic_rules.py +0 -0
  281. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_syntax_control_flow.py +0 -0
  282. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_syntax_coverage.py +0 -0
  283. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_syntax_expressions.py +0 -0
  284. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_taint.py +0 -0
  285. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_template_escaping.py +0 -0
  286. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_tornado_bottle_models.py +0 -0
  287. {coretrace_python_analyzer-0.14.0 → coretrace_python_analyzer-0.16.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 (2026-09-27)
4
+
5
+ ### Advisories
6
+
7
+ - 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).
8
+ - 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).
9
+ - 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).
10
+
11
+ ## 0.15.0 (2026-09-26)
12
+
13
+ ### Precision
14
+
15
+ - `open-redirect` judges a flow on the destination a browser resolves from the target's constant text, with its own criteria rather than SSRF's. A reference on the current site keeps the browser there, so the flow is refuted. Such a reference is `/` followed by a character that is not a slash, a backslash, a space or a control character, as in `"/profile/" + id`; or `?`, `#`, or a relative path whose first segment holds no `:`. A fixed absolute host makes the flow a hotspot, since the input chooses the path, where that host may redirect again. Everything else stays a vulnerability, including `"/" + next`: `//evil.com` is another host. The URL facts both rules read are one shared proof, `coretrace_python.taint.urls`, and `ssrf` now reads it too, with the same verdicts. The regression corpus is unchanged: its three `open-redirect` findings redirect to values built without constant text (#168).
16
+
17
+ ### Advisories
18
+
19
+ - An advisory entry point may declare a `host` condition: the attacker must choose the host of the URL passed as the named argument. It is decided from the URL's constant text, the proof `ssrf` and `open-redirect` read. A fixed host with redirects disabled in the call leaves the call reachable, not exploitable. A fixed host with redirects followed keeps it exploitable, because a redirect may lead to a host the attacker controls, and the condition pending review says so. Input in the query does not choose the host. Anything else stays pending review, and so do the environment conditions. A `host` condition must name its argument and carry nothing else (#170).
20
+
3
21
  ## 0.14.0 (2026-09-26)
4
22
 
5
23
  ### Precision
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.14.0
3
+ Version: 0.16.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
@@ -117,7 +117,9 @@ that must not leave the application through a log, a third party or plain storag
117
117
 
118
118
  A rule may know more about its own sinks than the refutation does: `judge(ctx, function,
119
119
  flow, verdict)` returns the rule's verdict on a flow, given the refutation's, and by
120
- default returns it unchanged. `ssrf` reads there how the URL is built. The refutation's
120
+ default returns it unchanged. `ssrf` and `open-redirect` read there what the URL's
121
+ constant text proves, a proof they share (`coretrace_python.taint.urls`) and each
122
+ judges by its own criteria. The refutation's
121
123
  verdict, which other consumers of the flow such as the advisory correlation read, does
122
124
  not change; a rule reading more analyses declares them in its `requires`.
123
125
 
@@ -155,7 +157,7 @@ class BottleModels(ModelPlugin):
155
157
  | `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. |
156
158
  | `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
157
159
  | `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. |
158
- | `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). |
160
+ | `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. |
159
161
  | `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. |
160
162
  | `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. |
161
163
  | `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
@@ -256,6 +258,10 @@ one, and the label of the entry point it is (`http` for a route, `argv` for a co
256
258
  when the models make it one. `ctx.call_graph(module)` gives each function's call sites
257
259
  (`sites(function)`) and the symbols it reads, called or not (`reads(function)`: reading
258
260
  `request.form['name']` reads `python.flask.request.form`), each where it first reads it.
261
+ `ctx.templates` gives the templates found under the project's `templates` directories:
262
+ their `names`, the filters they apply as `calls` (each a `FilterCall(symbol, span)`, the
263
+ function behind the filter and where the template applies it), and where they name a
264
+ template the analyzer cannot read (`unread`).
259
265
  The shipped `vulnerable-dependency`, `reachable-vulnerability` and `dependency-policy`
260
266
  plugins are of this kind.
261
267
 
@@ -117,7 +117,7 @@ variable is not treated that way yet.
117
117
  | `ssrf` | The URL of an HTTP client request (Requests, httpx, `urllib`), by position or as `url=`; the body, headers and query parameters are not the destination. How `+` and f-strings build the URL can prove part of its destination. When constant text fixes `scheme://host` and ends the authority with `/`, `?` or `#` before any input (`"https://api.example.com/users/" + id`, or a module-level string bound once and never rebound), the flow is a hotspot: the input still chooses the path, which may reach a sensitive resource, and a redirect may lead elsewhere. With the path fixed too, the input reaching the query or fragment only, and the call disabling redirects (`allow_redirects=False`, `follow_redirects=False`), the destination is fixed and the flow is refuted. Anything unproven stays a vulnerability: a base of unknown value (imported, a call result), a constant leaving the authority open (`"https:/"`, no `/` after the host, a variable port), `urljoin`, `%`, `format`, a URL built in a helper function. |
118
118
  | `xss` | An HTTP response body built without escaping. A Django template rendered by `render_to_string` escapes, where the analyzer can establish it (below). |
119
119
  | `insecure-deserialization` | `pickle`, `marshal`, `dill`, `jsonpickle`, `shelve`, and the YAML loaders (`yaml.load`, `load_all`, `full_load`, `unsafe_load` and their several-document versions); `yaml.load` and `load_all` given a safe loader (`SafeLoader`, `BaseLoader`) are not. |
120
- | `open-redirect` | An HTTP redirect target. |
120
+ | `open-redirect` | An HTTP redirect target, judged on the destination a browser resolves from the target's constant text, as `+` and f-strings build it. A reference on the current site keeps the browser there, where any further redirect is the project's own code, analysed on its own, so the flow is refuted. Such a reference is `/` followed by a character that is not a slash, a backslash, a space or a control character (`"/profile/" + id`), `?`, `#`, or a relative path whose first segment ends in the text without a `:`. A fixed absolute host (`"https://example.com/" + path`) sends the browser there, but the input chooses the path, where that host may redirect again: a hotspot. Anything else stays a vulnerability. That covers `"/" + next`, since `//evil.com` is another host, a host left open, a base of unknown value and a target built in a helper function. |
121
121
  | `plaintext-credential-storage` | A parameter named like a password stored in a database without hashing. Medium confidence, since a name is a hint. |
122
122
  | `exploitable-vulnerability` | An API affected by an advisory of a vulnerable requirement, see Dependencies. |
123
123
 
@@ -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.14.0"},
244
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.16.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.14.0",
276
+ "name": "coretrace-python-analyzer", "version": "0.16.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
@@ -386,6 +437,23 @@ a call passing a variable, or unpacking `*args` or `**kwargs`, leaves the condit
386
437
  pending review. A `semantic` condition — the document's tags, the platform, the
387
438
  template's origin — cannot be decided and is always pending review, never dropped.
388
439
 
440
+ A `host` condition says the attacker must choose the host of the URL passed as
441
+ `argument` (or at `position`), as credentials leaking to the host of a crafted URL
442
+ need: `{"kind": "host", "text": "…", "argument": "url", "position": 0}`. It is
443
+ decided from the URL's constant text, as `ssrf` and `open-redirect` read it, when the
444
+ attacker's input is passed in that argument. The outcome has three cases:
445
+
446
+ - Constant text fixes the host (`"https://api.example.com/" + path`) and the call
447
+ disables redirects (`allow_redirects=False`, `follow_redirects=False`): the call is
448
+ reachable, not exploitable.
449
+ - Constant text fixes the host but redirects may be followed: the call stays
450
+ exploitable. The fixed initial host does not clear the chain, since a redirect may
451
+ lead to a host the attacker controls, and the condition pending review says so.
452
+ Input in the query does not choose the host.
453
+ - Otherwise the condition stays pending review.
454
+
455
+ The other conditions, the environment included, stay pending as before.
456
+
389
457
  Every dependency finding records the highest level of evidence established in its
390
458
  `level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
391
459
  (a module of the package is imported somewhere), `reachable` (an entry point or affected
@@ -427,16 +495,25 @@ check, including findings the policy accepts or a comment suppresses:
427
495
  | Status | When |
428
496
  |---|---|
429
497
  | `affected` | The project's code reaches the vulnerable code. The notes give each place and its level, `reachable` or `exploitable`. |
430
- | `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. |
431
499
  | `under_investigation` | Anything else. The notes say which condition failed. |
432
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
+
433
510
  `not_affected` needs the lock-file check because the analyzer reads the project's code,
434
511
  not the code of installed packages. A framework calling the vulnerable function on the
435
512
  project's behalf would go unseen. Without a lock file, a package is never
436
513
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
437
514
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
438
515
  cannot know it. Its `tooling` names the analyzer and each component with its digest
439
- (`coretrace-python-analyzer 0.14.0; curated-advisories 2026.09.25 (sha256:…)`), so the
516
+ (`coretrace-python-analyzer 0.16.0; curated-advisories 2026.09.25 (sha256:…)`), so the
440
517
  `@id` changes with the advisory data a statement was decided with.
441
518
 
442
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.14.0"
7
+ version = "0.16.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.14.0"
3
+ __version__ = "0.16.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.
@@ -0,0 +1,47 @@
1
+ """Open redirect: attacker-controlled input reaching a open redirect sink.
2
+
3
+ The rule draws its verdict from what the target's constant text proves (``taint.urls``),
4
+ as a browser resolves it. A reference on the current site (``/profile/``, ``?page=``,
5
+ ``profile/``) keeps the browser on the site, where any further redirect is the project's
6
+ own code, analysed on its own: refuted. A fixed absolute host sends the browser there,
7
+ but the input chooses the path, where that host may redirect again: a hotspot. Anything
8
+ unproven, ``"/" + next`` included, stays a vulnerability.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import ClassVar
14
+
15
+ from coretrace_python.abstract.strings import ModuleStringsAnalysis
16
+ from coretrace_python.analysis import AnyAnalysis
17
+ from coretrace_python.findings import Severity
18
+ from coretrace_python.findings.refutation import Status, Verdict
19
+ from coretrace_python.hir import nodes
20
+ from coretrace_python.ir.ssa import SSAAnalysis
21
+ from coretrace_python.plugins import PluginContext, TaintDetector
22
+ from coretrace_python.taint import TaintFlow, TaintKind
23
+ from coretrace_python.taint.urls import flow_url
24
+
25
+
26
+ class OpenRedirectPlugin(TaintDetector):
27
+ name: ClassVar[str] = "open-redirect"
28
+ rule_id: ClassVar[str] = "open-redirect"
29
+ kind: ClassVar[TaintKind] = TaintKind.REDIRECT
30
+ severity: ClassVar[Severity] = Severity.MEDIUM
31
+ title: ClassVar[str] = "Open redirect"
32
+ requires: ClassVar[frozenset[AnyAnalysis]] = TaintDetector.requires | {SSAAnalysis, ModuleStringsAnalysis}
33
+
34
+ def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
35
+ ssa = ctx.get(SSAAnalysis, function)
36
+ defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
37
+ target = flow_url(flow, defs, ctx.get(ModuleStringsAnalysis))
38
+ if target.relative:
39
+ return Verdict(flow, Status.REFUTED, f"the browser stays on the site: the target starts with {target.text!r}")
40
+ if target.origin is not None and verdict.status is Status.VULNERABILITY:
41
+ return Verdict(
42
+ flow,
43
+ Status.HOTSPOT,
44
+ f"the browser goes to the host fixed by {target.origin!r}, but the input chooses the path, "
45
+ "where that host may redirect again",
46
+ )
47
+ return verdict
@@ -1,7 +1,7 @@
1
1
  name = "open-redirect"
2
2
  version = "1.0.0"
3
3
  plugin_api = ">=1,<2"
4
- requires = ["taint.flows", "findings.refutation"]
4
+ requires = ["taint.flows", "findings.refutation", "ir.ssa", "abstract.module_strings"]
5
5
  provides = ["vulnerability.open-redirect"]
6
6
 
7
7
  [entrypoint]
@@ -0,0 +1,49 @@
1
+ """Server-side request forgery: attacker-controlled input reaching a SSRF sink.
2
+
3
+ The rule draws its verdict from what the URL's constant text proves (``taint.urls``).
4
+ A fixed host keeps the input from choosing it, but the input still chooses the path,
5
+ which may reach a sensitive resource of that host, and a redirect may lead elsewhere: a
6
+ hotspot. With the path fixed too, the input reaching the query or fragment only, and
7
+ redirects disabled in the call, the destination is fixed: refuted. Anything unproven,
8
+ a base of unknown value or a URL built in a helper function, stays a vulnerability.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import ClassVar
14
+
15
+ from coretrace_python.abstract.strings import ModuleStringsAnalysis
16
+ from coretrace_python.analysis import AnyAnalysis
17
+ from coretrace_python.findings import Severity
18
+ from coretrace_python.findings.refutation import Status, Verdict
19
+ from coretrace_python.hir import nodes
20
+ from coretrace_python.ir.ssa import SSAAnalysis
21
+ from coretrace_python.plugins import PluginContext, TaintDetector
22
+ from coretrace_python.taint import TaintFlow, TaintKind
23
+ from coretrace_python.taint.urls import flow_url, redirects_disabled
24
+
25
+
26
+ class SsrfPlugin(TaintDetector):
27
+ name: ClassVar[str] = "ssrf"
28
+ rule_id: ClassVar[str] = "ssrf"
29
+ kind: ClassVar[TaintKind] = TaintKind.SSRF
30
+ severity: ClassVar[Severity] = Severity.HIGH
31
+ title: ClassVar[str] = "Server-side request forgery"
32
+ requires: ClassVar[frozenset[AnyAnalysis]] = TaintDetector.requires | {SSAAnalysis, ModuleStringsAnalysis}
33
+
34
+ def judge(self, ctx: PluginContext, function: nodes.Function, flow: TaintFlow, verdict: Verdict) -> Verdict:
35
+ ssa = ctx.get(SSAAnalysis, function)
36
+ defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
37
+ url = flow_url(flow, defs, ctx.get(ModuleStringsAnalysis))
38
+ if url.origin is None:
39
+ return verdict
40
+ no_redirects = redirects_disabled(flow.sink_arguments)
41
+ if url.path_fixed:
42
+ if no_redirects:
43
+ return Verdict(flow, Status.REFUTED, f"the destination is fixed by {url.text!r} and redirects are disabled")
44
+ left = "a redirect may lead elsewhere"
45
+ else:
46
+ left = "the input chooses the path" + ("" if no_redirects else ", and a redirect may lead elsewhere")
47
+ if verdict.status is not Status.VULNERABILITY:
48
+ return verdict
49
+ return Verdict(flow, Status.HOTSPOT, f"the host is fixed by {url.origin!r}, but {left}")
@@ -251,7 +251,7 @@ def _condition(entry: Mapping[str, Any]) -> Condition:
251
251
  default = entry.get("default", False)
252
252
  if not isinstance(default, bool):
253
253
  raise TypeError(f"condition default must be true or false, got {default!r}")
254
- return Condition(
254
+ condition = Condition(
255
255
  str(entry["kind"]),
256
256
  str(entry["text"]),
257
257
  None if entry.get("argument") is None else str(entry["argument"]),
@@ -259,3 +259,8 @@ def _condition(entry: Mapping[str, Any]) -> Condition:
259
259
  position,
260
260
  default,
261
261
  )
262
+ if condition.kind == "host" and (
263
+ (condition.argument is None and condition.position is None) or condition.values or condition.default
264
+ ):
265
+ raise ValueError("a host condition names the URL argument, by keyword or position, and nothing else")
266
+ return condition
@@ -10,7 +10,7 @@ are correlated here into one high-confidence ``exploitable-vulnerability`` findi
10
10
  from __future__ import annotations
11
11
 
12
12
  from collections.abc import Iterable, Mapping
13
- from dataclasses import dataclass
13
+ from dataclasses import dataclass, replace
14
14
 
15
15
  from coretrace_python.dependency.graph import (
16
16
  DIRECT,
@@ -24,6 +24,7 @@ from coretrace_python.findings.refutation import Status, Verdict, Verdicts
24
24
  from coretrace_python.interprocedural import Arguments, CallSite, ExternalSymbol
25
25
  from coretrace_python.semantic.symbols import SymbolId
26
26
  from coretrace_python.taint import Sink, TaintFlow, TaintKind
27
+ from coretrace_python.taint.urls import UrlProof, redirects_disabled
27
28
 
28
29
  Affected = Mapping[SymbolId, tuple[Advisory, ...]]
29
30
 
@@ -61,14 +62,33 @@ class ConditionCheck:
61
62
  passed: str | None = None
62
63
 
63
64
 
64
- def check_conditions(entry: AdvisoryEntryPoint | None, arguments: Arguments | None) -> ConditionCheck:
65
- """Decide ``entry``'s conditions against what the call's ``arguments`` denote."""
65
+ def check_conditions(
66
+ entry: AdvisoryEntryPoint | None,
67
+ arguments: Arguments | None,
68
+ url: UrlProof | None = None,
69
+ passed_as: frozenset[tuple[int | None, str | None]] = frozenset(),
70
+ ) -> ConditionCheck:
71
+ """Decide ``entry``'s conditions against what the call's ``arguments`` denote and, for
72
+ a ``host`` condition, what ``url`` proves of the value the attacker's input is passed
73
+ in (``passed_as``). A host the URL's constant text fixes rules the call out when the
74
+ call disables redirects; otherwise a redirect may still lead to a host the attacker
75
+ controls, and the condition stays pending with that uncertainty."""
66
76
 
67
77
  if entry is None:
68
78
  return ConditionCheck()
69
79
  met: list[Condition] = []
70
80
  pending: list[Condition] = []
71
81
  for condition in entry.conditions:
82
+ if condition.kind == "host":
83
+ origin = url.origin if url is not None and _passed_in(condition, passed_as) else None
84
+ if origin is None:
85
+ pending.append(condition)
86
+ elif redirects_disabled(arguments):
87
+ return ConditionCheck(tuple(met), tuple(pending), condition, repr(origin))
88
+ else:
89
+ text = f"the host is fixed by {origin!r}, but a redirect may lead to a host the attacker controls"
90
+ pending.append(replace(condition, text=text))
91
+ continue
72
92
  given = arguments.given(condition.argument, condition.position) if condition.checkable and arguments is not None else None
73
93
  if given is None:
74
94
  pending.append(condition)
@@ -84,6 +104,16 @@ def check_conditions(entry: AdvisoryEntryPoint | None, arguments: Arguments | No
84
104
  return ConditionCheck(tuple(met), tuple(pending))
85
105
 
86
106
 
107
+ def _passed_in(condition: Condition, passed_as: frozenset[tuple[int | None, str | None]]) -> bool:
108
+ """Whether a value passed as ``passed_as`` is the argument ``condition`` names."""
109
+
110
+ return any(
111
+ (keyword is not None and keyword == condition.argument)
112
+ or (keyword is None and position is not None and position == condition.position)
113
+ for position, keyword in passed_as
114
+ )
115
+
116
+
87
117
  def ruled_out(module: str, site: CallSite, check: ConditionCheck) -> str:
88
118
  """Which call a contradicted condition rules out, and why: ``app:12
89
119
  python.yaml.load(Loader=python.yaml.SafeLoader)``."""
@@ -120,9 +150,10 @@ def correlate(
120
150
  flows: Iterable[TaintFlow],
121
151
  verdicts: Verdicts | None,
122
152
  affected: Affected,
153
+ urls: Mapping[TaintFlow, UrlProof] | None = None,
123
154
  ) -> tuple[Finding, ...]:
124
155
  """Exploitable-vulnerability findings for the non-refuted ADVISORY flows of a function,
125
- one per advisory the sink is affected by."""
156
+ one per advisory the sink is affected by; ``urls`` holds what each flow's URL proves."""
126
157
 
127
158
  findings: list[Finding] = []
128
159
  for flow in flows:
@@ -136,7 +167,7 @@ def correlate(
136
167
  entry = advisory.entry_point(flow.sink.symbol)
137
168
  if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
138
169
  continue
139
- check = check_conditions(entry, flow.sink_arguments)
170
+ check = check_conditions(entry, flow.sink_arguments, (urls or {}).get(flow), flow.passed_as)
140
171
  if check.contradicted is None:
141
172
  findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
142
173
  return tuple(findings)
@@ -151,8 +151,10 @@ class Condition:
151
151
  ``argument`` condition names the argument by keyword, and by ``position`` when it may
152
152
  be passed positionally, and lists the ``values`` that satisfy it — symbols
153
153
  (``python.yaml.FullLoader``) or constants as Python writes them (``True``);
154
- ``default`` says an absent argument means a vulnerable value. A ``semantic``
155
- condition cannot be checked and is reported as pending review."""
154
+ ``default`` says an absent argument means a vulnerable value. A ``host`` condition
155
+ says the attacker must choose the host of the URL passed as ``argument`` (or at
156
+ ``position``); the engine decides it from what the URL's constant text proves. A
157
+ ``semantic`` condition cannot be checked and is reported as pending review."""
156
158
 
157
159
  kind: str
158
160
  text: str
@@ -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(