coretrace-python-analyzer 0.18.0__tar.gz → 0.20.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 (295) hide show
  1. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/CHANGELOG.md +31 -0
  2. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/docs/plugins.md +18 -7
  4. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/docs/usage.md +77 -13
  5. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/__init__.py +2 -0
  8. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/heap.py +4 -1
  9. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cache.py +14 -7
  10. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/advisories.py +4 -0
  11. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/correlation.py +16 -3
  12. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/graph.py +4 -1
  13. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/engine.py +95 -28
  14. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/coverage.py +6 -4
  15. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/interprocedural/__init__.py +2 -0
  16. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/interprocedural/callgraph.py +67 -7
  17. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/interprocedural/modulegraph.py +103 -9
  18. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/semantic/scopes.py +2 -2
  19. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/__init__.py +2 -0
  20. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/engine.py +106 -9
  21. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/templates.py +279 -29
  22. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/DjangoGoat.json +28 -4
  23. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/httpie.json +16 -4
  24. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/pygoat.json +35 -4
  25. coretrace_python_analyzer-0.20.0/tests/test_block_super.py +155 -0
  26. coretrace_python_analyzer-0.20.0/tests/test_entry_point_aliases.py +160 -0
  27. coretrace_python_analyzer-0.20.0/tests/test_function_aliases_api.py +84 -0
  28. coretrace_python_analyzer-0.20.0/tests/test_key_taint.py +229 -0
  29. coretrace_python_analyzer-0.20.0/tests/test_keyword_name_conditions.py +243 -0
  30. coretrace_python_analyzer-0.20.0/tests/test_module_identity.py +384 -0
  31. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_release.py +1 -1
  32. coretrace_python_analyzer-0.20.0/tests/test_settings_scope.py +109 -0
  33. coretrace_python_analyzer-0.20.0/tests/test_template_filter_overrides.py +385 -0
  34. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/.github/workflows/ci.yml +0 -0
  35. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/.github/workflows/release.yml +0 -0
  36. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/.gitignore +0 -0
  37. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/LICENSE +0 -0
  38. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/NOTICE +0 -0
  39. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/README.md +0 -0
  40. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/docs/architecture.md +0 -0
  41. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/examples/add.py +0 -0
  42. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/examples/canonical_symbols.py +0 -0
  43. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/__main__.py +0 -0
  44. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/constants.py +0 -0
  45. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/ranges.py +0 -0
  46. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/strings.py +0 -0
  47. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/abstract/values.py +0 -0
  48. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/analysis/__init__.py +0 -0
  49. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/analysis/manager.py +0 -0
  50. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/analysis/provider.py +0 -0
  51. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  52. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  53. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  54. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  55. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  56. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  57. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  59. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  60. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  61. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  62. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  63. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  64. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  65. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  66. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  67. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  68. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  70. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  71. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  72. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  73. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  74. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  75. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  76. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  77. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  78. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  79. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  80. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  81. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  83. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  84. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  85. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  86. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  87. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
  88. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  90. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  92. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
  94. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
  95. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  96. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  97. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  98. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  99. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  100. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  101. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  103. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  104. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  105. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  106. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  107. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  108. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  109. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  110. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  111. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  112. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  113. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  114. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  115. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  116. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  117. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  118. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  119. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  120. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  121. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  122. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  123. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  124. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  125. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cfg/__init__.py +0 -0
  126. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cfg/builder.py +0 -0
  127. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cfg/dominance.py +0 -0
  128. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cfg/model.py +0 -0
  129. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/cli.py +0 -0
  130. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  131. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  132. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dataflow/solver.py +0 -0
  133. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/__init__.py +0 -0
  134. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/policy.py +0 -0
  135. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/sbom.py +0 -0
  136. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/dependency/vex.py +0 -0
  137. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/__init__.py +0 -0
  138. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/baseline.py +0 -0
  139. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/model.py +0 -0
  140. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/provenance.py +0 -0
  141. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/refutation.py +0 -0
  142. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/findings/suppressions.py +0 -0
  143. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/frontend/__init__.py +0 -0
  144. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  145. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/frontend/parser.py +0 -0
  146. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/hir/__init__.py +0 -0
  147. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/hir/nodes.py +0 -0
  148. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/hir/visitors.py +0 -0
  149. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
  150. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/__init__.py +0 -0
  151. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/defuse.py +0 -0
  152. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/lowering.py +0 -0
  153. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/model.py +0 -0
  154. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/printer.py +0 -0
  155. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/ir/ssa.py +0 -0
  156. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/__init__.py +0 -0
  157. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/api.py +0 -0
  158. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/detectors.py +0 -0
  159. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/loader.py +0 -0
  160. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/manifest.py +0 -0
  161. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/registry.py +0 -0
  162. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/plugins/secrets.py +0 -0
  163. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/reporters/__init__.py +0 -0
  164. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/reporters/json_format.py +0 -0
  165. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/reporters/report.py +0 -0
  166. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/reporters/sarif.py +0 -0
  167. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/reporters/text.py +0 -0
  168. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/semantic/__init__.py +0 -0
  169. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/semantic/identity.py +0 -0
  170. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/semantic/imports.py +0 -0
  171. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/semantic/symbols.py +0 -0
  172. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/source/__init__.py +0 -0
  173. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/source/manager.py +0 -0
  174. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/source/model.py +0 -0
  175. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/models.py +0 -0
  176. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/routes.py +0 -0
  177. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/src/coretrace_python/taint/urls.py +0 -0
  178. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/Chatbot.json +0 -0
  179. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  180. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/InsecureGram.json +0 -0
  181. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  182. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/NPL-IA.json +0 -0
  183. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  184. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  185. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/dvpwa.json +0 -0
  186. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  187. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/healthchecks.json +0 -0
  188. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/luigi.json +0 -0
  189. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/my_gdb.json +0 -0
  190. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/network-topologer.json +0 -0
  191. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  192. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  193. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/expected/yt-fts.json +0 -0
  194. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  195. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  196. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  197. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  198. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/regression/repositories.toml +0 -0
  199. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_advisory_evidence.py +0 -0
  200. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_advisory_feeds.py +0 -0
  201. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_advisory_url_host.py +0 -0
  202. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_aiohttp_models.py +0 -0
  203. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_analysis_manager.py +0 -0
  204. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_architecture.py +0 -0
  205. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_argument_conditions.py +0 -0
  206. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_attacker_arguments.py +0 -0
  207. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_audit_detectors.py +0 -0
  208. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_base_patterns.py +0 -0
  209. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_baseline.py +0 -0
  210. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_cache.py +0 -0
  211. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_cfg.py +0 -0
  212. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_class_keywords_with_targets.py +0 -0
  213. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_cli.py +0 -0
  214. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_closures.py +0 -0
  215. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_code_injection.py +0 -0
  216. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_config_secrets_coverage.py +0 -0
  217. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_constants.py +0 -0
  218. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_correlation.py +0 -0
  219. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_credential_context.py +0 -0
  220. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_dataflow.py +0 -0
  221. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_declared_entry_points.py +0 -0
  222. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_dependencies.py +0 -0
  223. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_detectors.py +0 -0
  224. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_discovery.py +0 -0
  225. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_django_http_clients.py +0 -0
  226. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_django_routes.py +0 -0
  227. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_django_safe_values.py +0 -0
  228. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_docs.py +0 -0
  229. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_dominance.py +0 -0
  230. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_emit_ir.py +0 -0
  231. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_exceptions.py +0 -0
  232. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_factories_match_loop_else.py +0 -0
  233. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_fail_on.py +0 -0
  234. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_fastapi_cli_precision.py +0 -0
  235. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_frameworks.py +0 -0
  236. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_frontend_gaps.py +0 -0
  237. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_heap.py +0 -0
  238. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_hex_digests.py +0 -0
  239. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_hir_builder.py +0 -0
  240. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_imports.py +0 -0
  241. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_interprocedural.py +0 -0
  242. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_interprocedural_taint.py +0 -0
  243. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_ir.py +0 -0
  244. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_match_patterns.py +0 -0
  245. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_members.py +0 -0
  246. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_module_level.py +0 -0
  247. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_nonlocal_writeback.py +0 -0
  248. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_nosql_injection.py +0 -0
  249. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_open_redirect_destinations.py +0 -0
  250. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_operator_sources.py +0 -0
  251. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_packaging.py +0 -0
  252. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_parallel.py +0 -0
  253. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_parser.py +0 -0
  254. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_plaintext_credentials.py +0 -0
  255. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_plugins.py +0 -0
  256. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_project.py +0 -0
  257. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_project_models.py +0 -0
  258. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_project_validators.py +0 -0
  259. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_provenance.py +0 -0
  260. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_pyir_imports.py +0 -0
  261. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_read_entry_points.py +0 -0
  262. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_real_code_precision.py +0 -0
  263. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_redirect_targets.py +0 -0
  264. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_refutation.py +0 -0
  265. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_refutation_evidence.py +0 -0
  266. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_regression.py +0 -0
  267. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_relative_paths.py +0 -0
  268. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_remaining_limits.py +0 -0
  269. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_reporters.py +0 -0
  270. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_safe_arguments.py +0 -0
  271. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_scale.py +0 -0
  272. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_scopes.py +0 -0
  273. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_secrets_sbom.py +0 -0
  274. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_self_state.py +0 -0
  275. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_source_manager.py +0 -0
  276. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_ssa.py +0 -0
  277. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_ssrf_destinations.py +0 -0
  278. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_ssrf_targets.py +0 -0
  279. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_summary_sanitizers.py +0 -0
  280. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_suppressions.py +0 -0
  281. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_symbol_resolution.py +0 -0
  282. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_symbols.py +0 -0
  283. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_syntactic_rules.py +0 -0
  284. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_syntax_control_flow.py +0 -0
  285. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_syntax_coverage.py +0 -0
  286. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_syntax_expressions.py +0 -0
  287. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_taint.py +0 -0
  288. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_template_context_flows.py +0 -0
  289. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_template_escaping.py +0 -0
  290. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_template_filter_calls.py +0 -0
  291. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_template_request_processor.py +0 -0
  292. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_tornado_bottle_models.py +0 -0
  293. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_unreadable_templates.py +0 -0
  294. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_url_proof.py +0 -0
  295. {coretrace_python_analyzer-0.18.0 → coretrace_python_analyzer-0.20.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.0 (2026-09-30)
4
+
5
+ ### Advisories
6
+
7
+ - An advisory condition of kind `keyword_name` says the attacker must choose a keyword name of the call, as Django's ORM takes column aliases and lookups (`annotate(**{alias: expr})`); it carries its text and nothing else. The engine decides it from the keys of what the call expands with `**`, apart from the mapping's values: keys carrying attacker input meet it and the call is exploitable; constant keys, a dict literal with string keys, `dict(total=...)` or plain keywords, contradict it whatever the values carry, so the call is ruled out (`app.views:12 python.app.models.Item.objects.annotate(keywords=total)`, `(keywords absent)` for a call passing no keyword) and a project passing only those stays `not_affected`; keys the engine cannot establish, a `**extra` parameter, a mapping built elsewhere, `dict(zip(...))`, leave it pending review, so the call is reachable, and exploitable when the mapping's values carry input (#193).
8
+
9
+ ### Analysis
10
+
11
+ - The keys of a mapping carry taint of their own, apart from its values: a dict literal `{alias: expr}`, `dict([(alias, expr)])`, a comprehension `{k: expr for k in input}` and a subscript assignment `mapping[alias] = expr` taint the `keys` location of the mapping's abstract object, which `{**mapping}`, `dict(mapping)` and `dict(**mapping)` copy; the value taint of a literal is what it was. A call expanding such a mapping with `**` passes its keys as the keyword names of the call, a way of passing only an advisory sink reads. Keys through a project wrapper are not followed; `mapping.update(other)`, `mapping.setdefault(k, v)`, `for k in mapping` and `mapping.keys()` carry no key taint; a mapping bound by a `Phi` is unknown (#193).
12
+
13
+ ### Plugins
14
+
15
+ - `Arguments.keyword_unpacked` says a call expands a `**mapping` whose keys it does not write; a `**` literal with constant keys that nothing else uses is recorded as its keywords, so `given()` decides safe arguments and argument conditions for it. The cache format changes.
16
+
17
+ ## 0.19.0 (2026-09-29)
18
+
19
+ ### Analysis
20
+
21
+ - Two files with the same Python module name, `a/app.py` and `z/app.py` outside any package, no longer overwrite each other: both are discovered, analysed and covered whatever the order they are found in. The engine tells them apart by their path from the root as a module name, `a.app` and `z.app`, in the module graph, the summary index, the cache keys and what project plugins see; a unique name stays what it was. An import of such a name resolves to the file in the importer's own root only, and one no root resolves is not followed. Each colliding file carries an `ambiguous-module` note and is covered as `ambiguous`, since a model or a route naming `python.app.main` applies to every definition of that symbol, and so does an importer whose import stayed unresolved; a VEX statement over such a project stays `under_investigation` (#182).
22
+
23
+ ### Precision
24
+
25
+ - A `TEMPLATES` assigned inside a function or a class no longer counts as Django settings: only a module-level assignment can activate the `request` context processor, and only a mention of the module's `TEMPLATES` elsewhere leaves that activation uncertain. A function-local `TEMPLATES`, called or not, used to enable the implicit request source, upgrading a template filter finding to exploitable (#184).
26
+ - A template block that renders `{{ block.super }}` keeps the parent block's content, so the filters the parent applies stay fed by the render context, with the parent template as the place of the call; a block without it still replaces the parent's whole block, and a chain of templates keeps exactly the parents each level renders. The parent's filters used to drop out of the exploitable flow as soon as a child defined the block (#185).
27
+ - A project tag library registering a filter of a built-in's name replaces the built-in in every Django template loading it before applying the filter, as Django's parser does: `{% load custom %}{{ bio|striptags }}` calls the project's `striptags` when `app/templatetags/custom.py` registers one, so it is no longer a reachable or exploitable call to `django.template.defaultfilters.striptags`; the project's function is ordinary code, analysed as any other. A load affects only the filters after it, a later load overrides an earlier one, `{% load striptags from custom %}` replaces only what it names, and Django's own libraries change nothing. The analyzer reads the registrations on `register`, a `django.template.Library` however it is imported, in decorator and call form, at module level or under `if`, `try` or a function. Where it cannot tell what a library registers — no such library file, one it cannot read or parse, a name not written as a constant, `register` passed elsewhere or filled through `filters=` or `register.filters`, or two apps giving the name and registering different filters — a filter of that name applied under the load is neither the built-in nor certainly replaced: no call is claimed, and the load counts among the templates the analyzer could not read (`app/templates/app/profile.html:1 loads 'custom', not read`), so an advisory naming the filter stays `under_investigation` in the VEX document. The settings' `OPTIONS["libraries"]` and `OPTIONS["builtins"]` are not read (#183).
28
+
29
+ ### Plugins
30
+
31
+ - `ModuleFunction.aliases` gives a project plugin the module-level names bound to a function by assigning one name to another, transitively and in source order, so an integration declaring a handler by the name a module binds (`handlers.main`) can report a name the engine resolves to no function; a method or a nested function has none. The cache format changes.
32
+ - An `EntryPoint` naming a project function or class applies through a module-level alias of it: `main = actual` makes `python.app.handlers.main` name `actual`, so a deployment declaring `handlers.main` reaches the function doing the work. An alias is an assignment of one name to another at module level, followed transitively and in source order, in a conditional or a `try` as well; a name bound inside a function or class, or to a call, an import or anything but a name, is no alias, and the engine still infers nothing from a name. A model naming a method wins over one naming its class, whichever name it uses.
33
+
3
34
  ## 0.18.0 (2026-09-28)
4
35
 
5
36
  ### Precision
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.18.0
3
+ Version: 0.20.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
@@ -53,7 +53,11 @@ resolved through imports and aliases, so `python.os.system` covers `os.system`,
53
53
  symbol, so a chain such as `sqlite3.connect(p).cursor().execute` is
54
54
  `python.sqlite3.connect.cursor.execute`, and `app = Flask(__name__)` gives `app.route` the
55
55
  symbol `python.flask.Flask.route`. Builtins are `python.builtins.<name>`. Symbols are
56
- `SymbolId` values from `coretrace_python.semantic.symbols`.
56
+ `SymbolId` values from `coretrace_python.semantic.symbols`. A function of the project is
57
+ `python.` followed by its module's import name and its qualified name,
58
+ `python.app.handlers.main`; two files with the same import name, `a/app.py` and
59
+ `z/app.py` outside any package, define the same symbols, so a model naming
60
+ `python.app.main` names the function of each.
57
61
 
58
62
  ## Kinds of plugin
59
63
 
@@ -190,7 +194,9 @@ A model plugin may also carry `advisories`, a tuple of `Advisory` values
190
194
  (`coretrace_python.dependency`) with the package, the vulnerable range and the affected
191
195
  symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
192
196
  reported, calls to the affected symbols become reachable vulnerabilities and tainted
193
- calls become exploitable ones.
197
+ calls become exploitable ones. An advisory's entry points carry `conditions`, of kind
198
+ `argument`, `host`, `semantic` or `keyword_name`, and `attacker_arguments`, decided as
199
+ the [usage guide](usage.md) describes.
194
200
 
195
201
  ### Reclassifying findings: `ProjectPlugin.refine`
196
202
 
@@ -255,14 +261,19 @@ module graph, the dependency graph, every advisory the plugins and advisory file
255
261
  contributed, the policy, the project root, and each module's imports, call graph and
256
262
  functions. `ctx.functions(module)` gives every function as the call graph names it
257
263
  (`Admin.get`, `outer.inner`, `<module>`), with its span, so a finding can be placed in
258
- one, and the label of the entry point it is (`http` for a route, `argv` for a command)
259
- when the models make it one. `ctx.call_graph(module)` gives each function's call sites
264
+ one, the label of the entry point it is (`http` for a route, `argv` for a command)
265
+ when the models make it one, and its `aliases`, the module-level names bound to it by
266
+ assigning one name to another (`main = actual`), transitively and in source order, so
267
+ an integration declaring a handler by the name a module binds can tell a name the
268
+ engine resolves to no function; a method or a nested function has none. `ctx.call_graph(module)` gives each function's call sites
260
269
  (`sites(function)`) and the symbols it reads, called or not (`reads(function)`: reading
261
270
  `request.form['name']` reads `python.flask.request.form`), each where it first reads it.
262
271
  `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`).
272
+ their `names`, the filters of Django's own libraries they apply as `calls` (each a
273
+ `FilterCall(symbol, span)`, the function behind the filter and where the template
274
+ applies it; a filter a project tag library the template loads replaces is no call),
275
+ and where they name a template the analyzer cannot read, or load a library it cannot
276
+ read while applying a filter of that library's name (`unread`).
266
277
  The shipped `vulnerable-dependency`, `reachable-vulnerability` and `dependency-policy`
267
278
  plugins are of this kind.
268
279
 
@@ -65,7 +65,13 @@ coretrace-python-analyzer [--check | --emit-ir [--ssa]] [options] [path]
65
65
  ## What is analysed
66
66
 
67
67
  Given a directory, every `.py` file below it is a module of one project, named after
68
- its package (`app/views.py` is `app.views`). Hidden directories, `node_modules`,
68
+ its package (`app/views.py` is `app.views`). Two files that would share a name, `a/app.py`
69
+ and `z/app.py` outside any package, are told apart by their path from the root, `a.app`
70
+ and `z.app`, and reported: each carries an `ambiguous-module` note, since a symbol
71
+ `python.app.main` may name a function of either and an import of `app` resolves only
72
+ from the importer's own directory, and each is covered as `ambiguous` rather than
73
+ analysed. An import of such a name that no file of the importer's own directory answers
74
+ is noted where it is written and not followed. Hidden directories, `node_modules`,
69
75
  `__pycache__`, `build`, `dist` and any virtual environment, recognised by its
70
76
  `pyvenv.cfg` whatever its name, are skipped. Dependency files at the root
71
77
  (`requirements*.txt`, `pyproject.toml`, `poetry.lock`, `uv.lock`) are read into a
@@ -241,7 +247,7 @@ the directory of the checked file; a path outside it is printed as it is.
241
247
  ```json
242
248
  {
243
249
  "schema_version": 1,
244
- "tool": {"name": "coretrace-python-analyzer", "version": "0.18.0"},
250
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.20.0"},
245
251
  "root": "/home/me/project",
246
252
  "findings": [
247
253
  {
@@ -261,7 +267,9 @@ the directory of the checked file; a path outside it is printed as it is.
261
267
  }
262
268
  ```
263
269
 
264
- `root` is the directory the paths are relative to.
270
+ `root` is the directory the paths are relative to. A file's `status` is `analysed`,
271
+ `ambiguous` (analysed, but another file has its module name, see above), `syntax-error`
272
+ or `unreadable`; only `analysed` files count in `files_analysed`.
265
273
 
266
274
  A directory check also lists, in `tool.components`, what the result was produced with
267
275
  besides the engine and the sources: each plugin it loaded, by manifest name and version,
@@ -273,7 +281,7 @@ bytes. A single-file check lists no components.
273
281
 
274
282
  ```json
275
283
  "tool": {
276
- "name": "coretrace-python-analyzer", "version": "0.18.0",
284
+ "name": "coretrace-python-analyzer", "version": "0.20.0",
277
285
  "components": [
278
286
  {"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
279
287
  {"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
@@ -372,6 +380,28 @@ Django. Most filters are functions of the same name in `django.template.defaultf
372
380
  are `<name>_filter` there, and the filters of `i18n`, `l10n`, `tz` and `humanize` are in
373
381
  their library's module (`timezone` is `django.templatetags.tz.do_timezone`).
374
382
 
383
+ A project tag library, a module of a `templatetags` directory, that registers a filter
384
+ of that name replaces the built-in in every template loading it, as Django's parser
385
+ does: `{% load custom %}{{ bio|striptags }}` calls the project's function when
386
+ `custom.py` registers a `striptags` filter, which is no call to the built-in; the
387
+ project's function is ordinary code, whose own calls are analysed as any other. A
388
+ load affects only the filters applied after it in the file, a later load overrides an
389
+ earlier one, `{% load striptags from custom %}` replaces only the filters it names, and
390
+ Django's own libraries (`static`, `i18n`, `l10n`, `tz`, `cache`, `humanize`) change
391
+ nothing. The analyzer reads the registrations on the library's `register`, a
392
+ `django.template.Library` however it is imported: `@register.filter`,
393
+ `@register.filter("name")`, `@register.filter(name="name", is_safe=True)`,
394
+ `register.filter("name", function)` and `register.filter(function)`, at module level or
395
+ under `if`, `try` or a function. Where it cannot tell what a library registers, the
396
+ filter is neither the built-in nor certainly replaced: a library found in no
397
+ `templatetags` directory (one an installed package ships, or that the settings'
398
+ `OPTIONS["libraries"]` or `OPTIONS["builtins"]` name, which are not read), a file it
399
+ cannot read or parse, a name given by a variable or unpacked arguments, `register`
400
+ passed to another function, filled through `filters=` or `register.filters`, or bound
401
+ to anything but a `Library`, or two apps giving the same library name and registering
402
+ different filters. The analyzer then claims no call, and counts the template among those
403
+ it could not read (below) when a filter of the library's name is applied under the load.
404
+
375
405
  Attacker input in the render context makes the filter exploitable at the render call:
376
406
  `render(request, "app/profile.html", {"bio": request.POST["bio"]})` with
377
407
  `{{ bio|striptags }}`. The analyzer links a context value to a filter only where it is
@@ -386,7 +416,8 @@ certain of it:
386
416
  arguments of the filters before it, or the output of a `{% filter %}` block. A name a
387
417
  tag binds shadows the context, and `{% include ... only %}` passes none of it.
388
418
  - The filter renders: a template extending another renders only its blocks, which
389
- replace the blocks of that name in what it extends.
419
+ replace the blocks of that name in what it extends, unless the block renders
420
+ `{{ block.super }}`, which keeps the parent's content and the filters it applies.
390
421
  - One file has the template's name; of two files of the same name, which one renders
391
422
  depends on the loaders.
392
423
 
@@ -399,9 +430,12 @@ With the `django.template.context_processors.request` context processor, a templ
399
430
  rendered with the request reads it as `request`: `{{ request.GET.q|striptags }}` is
400
431
  exploitable at the render call too. The analyzer counts it only where all of these hold:
401
432
 
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.
433
+ - The project's settings assign `TEMPLATES` a literal list at module level, every
434
+ `DjangoTemplates` engine of it lists the processor in a literal `context_processors`,
435
+ and no other code of the project names the module's `TEMPLATES`, or a `TEMPLATES` it
436
+ does not bind, as a star import gives. A `TEMPLATES` bound in a function or a class
437
+ is another name: it configures nothing and changes nothing.
438
+ A mere mention of the processor proves nothing.
405
439
  - The render call passes the request: `render` and `TemplateResponse` always do,
406
440
  `render_to_string` when given `request`, `SimpleTemplateResponse` never.
407
441
  - The render's context is certain, absent or a dict literal as above, so it cannot hide
@@ -454,6 +488,32 @@ attacker's input is passed in that argument. The outcome has three cases:
454
488
 
455
489
  The other conditions, the environment included, stay pending as before.
456
490
 
491
+ A `keyword_name` condition says the attacker must choose a keyword name of the call,
492
+ as Django's ORM takes column aliases and lookups (`annotate(**{alias: expr})`):
493
+ `{"kind": "keyword_name", "text": "…"}`, and nothing else. The engine decides it from
494
+ the keys of what the call expands with `**`; the values of the mapping do not decide
495
+ it. The outcome has three cases:
496
+
497
+ - Keys carrying attacker input meet it, and the call is exploitable. Key taint is
498
+ carried, apart from value taint, for a dict literal `{alias: expr}`,
499
+ `dict([(alias, expr)])`, a comprehension `{k: expr for k in request.GET}` and a
500
+ subscript assignment `mapping[alias] = expr`, and copied by `{**mapping}` and
501
+ `dict(mapping)`.
502
+ - Constant keys — a dict literal with string keys, `dict(total=…)`, plain keywords —
503
+ contradict it, whatever the values carry: the call is ruled out
504
+ (`app.views:12 python.app.models.Item.objects.annotate(keywords=total)`, or
505
+ `(keywords absent)` for a call passing no keyword), and a project passing only those
506
+ stays `not_affected`.
507
+ - Keys the engine cannot establish — a `**extra` parameter, a mapping built elsewhere
508
+ or returned by a function, `dict(zip(...))`, `**request.GET` — leave it pending
509
+ review, as any unknown argument does: the call is reachable, and exploitable when the
510
+ mapping's values carry attacker input.
511
+
512
+ Keys passed through a project wrapper stay pending, and `mapping.update(other)`,
513
+ `mapping.setdefault(k, v)`, `for k in mapping` and `mapping.keys()` carry no key
514
+ taint. A literal the function fills after building it (`m = {}; m[alias] = expr`)
515
+ has keys the literal does not write, so the call is not ruled out.
516
+
457
517
  Every dependency finding records the highest level of evidence established in its
458
518
  `level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
459
519
  (a module of the package is imported somewhere), `reachable` (an entry point or affected
@@ -502,10 +562,14 @@ A template the analyzer cannot read may apply any filter. The project names one
502
562
  `render`, `render_to_string`, `TemplateResponse`, an `{% include %}` or an `{% extends %}`
503
563
  names a template by an expression, or by a name found under no `templates` directory,
504
564
  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.
565
+ too, and so does a template loading a tag library the analyzer cannot read, or find,
566
+ or finds in several apps registering different filters, where it then applies a filter
567
+ of a built-in's name, since the library may replace it. An advisory whose entry points
568
+ include a template filter then stays `under_investigation`, and the notes list where
569
+ the project names each such template (`app.views:12 names a template by an
570
+ expression`, `app/templates/app/profile.html:1 loads 'crispy_forms_tags', not found`).
571
+ A template named elsewhere, such as a class-based view's `template_name`, is not
572
+ checked.
509
573
 
510
574
  `not_affected` needs the lock-file check because the analyzer reads the project's code,
511
575
  not the code of installed packages. A framework calling the vulnerable function on the
@@ -513,7 +577,7 @@ project's behalf would go unseen. Without a lock file, a package is never
513
577
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
514
578
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
515
579
  cannot know it. Its `tooling` names the analyzer and each component with its digest
516
- (`coretrace-python-analyzer 0.18.0; curated-advisories 2026.09.25 (sha256:…)`), so the
580
+ (`coretrace-python-analyzer 0.20.0; curated-advisories 2026.09.25 (sha256:…)`), so the
517
581
  `@id` changes with the advisory data a statement was decided with.
518
582
 
519
583
  ## Large projects
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.18.0"
7
+ version = "0.20.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.18.0"
3
+ __version__ = "0.20.0"
4
4
 
@@ -8,6 +8,7 @@ from coretrace_python.abstract.constants import (
8
8
  from coretrace_python.abstract.heap import (
9
9
  ATTRIBUTES,
10
10
  ELEMENTS,
11
+ KEYS,
11
12
  MUTATORS,
12
13
  AbstractObject,
13
14
  AliasSet,
@@ -24,6 +25,7 @@ from coretrace_python.abstract.values import AbstractValue, Truth
24
25
  __all__ = [
25
26
  "ATTRIBUTES",
26
27
  "ELEMENTS",
28
+ "KEYS",
27
29
  "MUTATORS",
28
30
  "AbstractObject",
29
31
  "AbstractValue",
@@ -6,7 +6,8 @@ globals and imported symbols by name, and the fields loaded from an object by fi
6
6
  Each value points to an ``AliasSet`` of objects, computed by a flow-insensitive
7
7
  points-to fixpoint over the SSA form; each object has two ``HeapLocation`` fields,
8
8
  ``elements`` for its items and ``attributes`` for its attributes, field-insensitive
9
- within each. Taint and dependence analyses key their states by these locations, so a
9
+ within each, and a third, ``keys``, for the keys of a mapping, which only the taint
10
+ engine fills. Taint and dependence analyses key their states by these locations, so a
10
11
  store, a mutating method call or a load on any alias reads and writes the same place.
11
12
  """
12
13
 
@@ -47,6 +48,8 @@ from coretrace_python.source import SourceSpan
47
48
 
48
49
  ELEMENTS = "elements"
49
50
  ATTRIBUTES = "attributes"
51
+ # The keys of a mapping, stored by the taint engine; the points-to solver never fills it.
52
+ KEYS = "keys"
50
53
 
51
54
  # Method names that store their arguments into the receiver's elements.
52
55
  MUTATORS = frozenset(
@@ -42,7 +42,7 @@ from coretrace_python.interprocedural import (
42
42
  from coretrace_python.semantic.symbols import SymbolId
43
43
  from coretrace_python.source import SourceId, SourceSpan
44
44
 
45
- CACHE_FORMAT = 10
45
+ CACHE_FORMAT = 12
46
46
 
47
47
 
48
48
  @dataclass(frozen=True)
@@ -134,7 +134,7 @@ class ProjectCache:
134
134
  def encode(module: CachedModule) -> dict[str, Any]:
135
135
  return {
136
136
  "format": CACHE_FORMAT,
137
- "functions": [[f.name, _encode_span(f.span), f.entry_point] for f in module.functions],
137
+ "functions": [[f.name, _encode_span(f.span), f.entry_point, list(f.aliases)] for f in module.functions],
138
138
  "summaries": {name: _encode_summary(s) for name, s in module.summaries.items()},
139
139
  "sites": [_encode_site(site) for site in module.sites],
140
140
  "findings": [_encode_finding(finding) for finding in module.findings],
@@ -196,8 +196,13 @@ def _decode_span(data: Any) -> SourceSpan:
196
196
 
197
197
 
198
198
  def _decode_function(data: Any) -> ModuleFunction:
199
- name, span, entry_point = data
200
- return ModuleFunction(_string(name), _decode_span(span), None if entry_point is None else _string(entry_point))
199
+ name, span, entry_point, aliases = data
200
+ return ModuleFunction(
201
+ _string(name),
202
+ _decode_span(span),
203
+ None if entry_point is None else _string(entry_point),
204
+ tuple(_string(alias) for alias in aliases),
205
+ )
201
206
 
202
207
 
203
208
  def _decode_read(data: Any) -> SymbolRead:
@@ -363,6 +368,7 @@ def _encode_arguments(arguments: Arguments) -> dict[str, Any]:
363
368
  "positional": list(arguments.positional),
364
369
  "keywords": [[name, value] for name, value in arguments.keywords],
365
370
  "unpacked": arguments.unpacked,
371
+ "keyword_unpacked": arguments.keyword_unpacked,
366
372
  }
367
373
 
368
374
 
@@ -370,11 +376,12 @@ def _decode_arguments(data: Mapping[str, Any]) -> Arguments:
370
376
  def denoted(value: Any) -> str | None:
371
377
  return None if value is None else _string(value)
372
378
 
373
- unpacked = data["unpacked"]
374
- if not isinstance(unpacked, bool):
375
- raise TypeError(f"expected a boolean, got {unpacked!r}")
379
+ unpacked, keyword_unpacked = data["unpacked"], data["keyword_unpacked"]
380
+ if not isinstance(unpacked, bool) or not isinstance(keyword_unpacked, bool):
381
+ raise TypeError(f"expected booleans, got {unpacked!r} and {keyword_unpacked!r}")
376
382
  return Arguments(
377
383
  tuple(denoted(value) for value in data["positional"]),
378
384
  tuple((_string(name), denoted(value)) for name, value in data["keywords"]),
379
385
  unpacked,
386
+ keyword_unpacked,
380
387
  )
@@ -263,4 +263,8 @@ def _condition(entry: Mapping[str, Any]) -> Condition:
263
263
  (condition.argument is None and condition.position is None) or condition.values or condition.default
264
264
  ):
265
265
  raise ValueError("a host condition names the URL argument, by keyword or position, and nothing else")
266
+ if condition.kind == "keyword_name" and (
267
+ condition.argument is not None or condition.position is not None or condition.values or condition.default
268
+ ):
269
+ raise ValueError("a keyword_name condition carries its text and nothing else")
266
270
  return condition
@@ -23,7 +23,7 @@ from coretrace_python.findings import Confidence, Finding, Severity
23
23
  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
- from coretrace_python.taint import Sink, TaintFlow, TaintKind
26
+ from coretrace_python.taint import KEYWORD_NAMES, Sink, TaintFlow, TaintKind
27
27
  from coretrace_python.taint.urls import UrlProof, redirects_disabled
28
28
 
29
29
  Affected = Mapping[SymbolId, tuple[Advisory, ...]]
@@ -72,13 +72,25 @@ def check_conditions(
72
72
  a ``host`` condition, what ``url`` proves of the value the attacker's input is passed
73
73
  in (``passed_as``). A host the URL's constant text fixes rules the call out when the
74
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."""
75
+ controls, and the condition stays pending with that uncertainty. A ``keyword_name``
76
+ condition is met when the attacker's input is passed as the keys of a ``**`` mapping,
77
+ pending when the call expands a mapping whose keys are not established, and
78
+ contradicted by a call writing every keyword name."""
76
79
 
77
80
  if entry is None:
78
81
  return ConditionCheck()
79
82
  met: list[Condition] = []
80
83
  pending: list[Condition] = []
81
84
  for condition in entry.conditions:
85
+ if condition.kind == "keyword_name":
86
+ if any(keyword == KEYWORD_NAMES for _, keyword in passed_as):
87
+ met.append(condition)
88
+ elif arguments is None or arguments.keyword_unpacked:
89
+ pending.append(condition)
90
+ else:
91
+ names = ", ".join(sorted({name for name, _ in arguments.keywords}))
92
+ return ConditionCheck(tuple(met), tuple(pending), condition, names or None)
93
+ continue
82
94
  if condition.kind == "host":
83
95
  origin = url.origin if url is not None and _passed_in(condition, passed_as) else None
84
96
  if origin is None:
@@ -119,7 +131,8 @@ def ruled_out(module: str, site: CallSite, check: ConditionCheck) -> str:
119
131
  python.yaml.load(Loader=python.yaml.SafeLoader)``."""
120
132
 
121
133
  assert check.contradicted is not None and isinstance(site.target, ExternalSymbol)
122
- argument = check.contradicted.argument or f"#{check.contradicted.position}"
134
+ contradicted = check.contradicted
135
+ argument = contradicted.argument or ("keywords" if contradicted.kind == "keyword_name" else f"#{contradicted.position}")
123
136
  passed = f"{argument} absent" if check.passed is None else f"{argument}={check.passed}"
124
137
  return f"{module}:{site.location.start_line} {site.target.symbol}({passed})"
125
138
 
@@ -154,7 +154,10 @@ class Condition:
154
154
  ``default`` says an absent argument means a vulnerable value. A ``host`` condition
155
155
  says the attacker must choose the host of the URL passed as ``argument`` (or at
156
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."""
157
+ ``keyword_name`` condition says the attacker must choose a keyword name of the call,
158
+ as an ORM alias expanded from a mapping; the engine decides it from the keys of what
159
+ the call expands with ``**``. A ``semantic`` condition cannot be checked and is
160
+ reported as pending review."""
158
161
 
159
162
  kind: str
160
163
  text: str