coretrace-python-analyzer 0.16.0__tar.gz → 0.18.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.16.0 → coretrace_python_analyzer-0.18.0}/CHANGELOG.md +16 -0
  2. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/docs/plugins.md +2 -1
  4. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/docs/usage.md +6 -6
  5. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/engine.py +7 -0
  8. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/interprocedural/__init__.py +4 -0
  9. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/interprocedural/callgraph.py +52 -7
  10. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/interprocedural/summaries.py +6 -0
  11. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/__init__.py +2 -0
  12. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/engine.py +49 -11
  13. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/models.py +50 -3
  14. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/templates.py +11 -2
  15. coretrace_python_analyzer-0.18.0/tests/test_base_patterns.py +326 -0
  16. coretrace_python_analyzer-0.18.0/tests/test_declared_entry_points.py +155 -0
  17. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_release.py +1 -1
  18. coretrace_python_analyzer-0.18.0/tests/test_unreadable_templates.py +70 -0
  19. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/.github/workflows/ci.yml +0 -0
  20. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/.github/workflows/release.yml +0 -0
  21. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/.gitignore +0 -0
  22. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/LICENSE +0 -0
  23. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/NOTICE +0 -0
  24. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/README.md +0 -0
  25. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/docs/architecture.md +0 -0
  26. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/examples/add.py +0 -0
  27. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/examples/canonical_symbols.py +0 -0
  28. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/__main__.py +0 -0
  29. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/__init__.py +0 -0
  30. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/constants.py +0 -0
  31. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/heap.py +0 -0
  32. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/ranges.py +0 -0
  33. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/strings.py +0 -0
  34. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/abstract/values.py +0 -0
  35. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/analysis/__init__.py +0 -0
  36. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/analysis/manager.py +0 -0
  37. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/analysis/provider.py +0 -0
  38. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  39. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  40. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  41. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  42. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  43. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  44. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  45. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  46. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  47. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  48. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  49. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  50. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  51. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  52. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  53. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  54. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  55. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  56. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  57. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  59. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  60. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  61. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  62. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  63. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  64. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  65. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  66. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  67. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  68. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  70. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  71. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  72. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  73. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  74. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
  75. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
  76. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  77. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  78. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  79. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  80. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
  81. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  83. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  84. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  85. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  86. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  87. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  88. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  90. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  92. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  94. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  95. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  97. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  99. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  100. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  101. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  103. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  104. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  106. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  107. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  108. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  109. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  110. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  111. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  112. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cache.py +0 -0
  113. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cfg/__init__.py +0 -0
  114. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cfg/builder.py +0 -0
  115. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cfg/dominance.py +0 -0
  116. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cfg/model.py +0 -0
  117. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/cli.py +0 -0
  118. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  119. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  120. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dataflow/solver.py +0 -0
  121. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/__init__.py +0 -0
  122. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/advisories.py +0 -0
  123. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/correlation.py +0 -0
  124. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/graph.py +0 -0
  125. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/policy.py +0 -0
  126. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/sbom.py +0 -0
  127. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/dependency/vex.py +0 -0
  128. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/__init__.py +0 -0
  129. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/baseline.py +0 -0
  130. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/coverage.py +0 -0
  131. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/model.py +0 -0
  132. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/provenance.py +0 -0
  133. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/refutation.py +0 -0
  134. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/findings/suppressions.py +0 -0
  135. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/frontend/__init__.py +0 -0
  136. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  137. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/frontend/parser.py +0 -0
  138. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/hir/__init__.py +0 -0
  139. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/hir/nodes.py +0 -0
  140. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/hir/visitors.py +0 -0
  141. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  142. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/__init__.py +0 -0
  143. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/defuse.py +0 -0
  144. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/lowering.py +0 -0
  145. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/model.py +0 -0
  146. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/printer.py +0 -0
  147. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/ir/ssa.py +0 -0
  148. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/__init__.py +0 -0
  149. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/api.py +0 -0
  150. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/detectors.py +0 -0
  151. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/loader.py +0 -0
  152. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/manifest.py +0 -0
  153. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/registry.py +0 -0
  154. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/plugins/secrets.py +0 -0
  155. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/reporters/__init__.py +0 -0
  156. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/reporters/json_format.py +0 -0
  157. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/reporters/report.py +0 -0
  158. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/reporters/sarif.py +0 -0
  159. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/reporters/text.py +0 -0
  160. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/semantic/__init__.py +0 -0
  161. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/semantic/identity.py +0 -0
  162. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/semantic/imports.py +0 -0
  163. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/semantic/scopes.py +0 -0
  164. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/semantic/symbols.py +0 -0
  165. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/source/__init__.py +0 -0
  166. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/source/manager.py +0 -0
  167. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/source/model.py +0 -0
  168. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/routes.py +0 -0
  169. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/src/coretrace_python/taint/urls.py +0 -0
  170. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/Chatbot.json +0 -0
  171. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/DjangoGoat.json +0 -0
  172. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  173. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/InsecureGram.json +0 -0
  174. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  175. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/NPL-IA.json +0 -0
  176. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  177. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  178. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/dvpwa.json +0 -0
  179. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  180. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/healthchecks.json +0 -0
  181. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/httpie.json +0 -0
  182. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/luigi.json +0 -0
  183. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/my_gdb.json +0 -0
  184. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/network-topologer.json +0 -0
  185. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/pygoat.json +0 -0
  186. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  187. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  188. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/expected/yt-fts.json +0 -0
  189. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  190. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  191. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  192. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  193. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/regression/repositories.toml +0 -0
  194. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_advisory_evidence.py +0 -0
  195. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_advisory_feeds.py +0 -0
  196. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_advisory_url_host.py +0 -0
  197. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_aiohttp_models.py +0 -0
  198. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_analysis_manager.py +0 -0
  199. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_architecture.py +0 -0
  200. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_argument_conditions.py +0 -0
  201. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_attacker_arguments.py +0 -0
  202. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_audit_detectors.py +0 -0
  203. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_baseline.py +0 -0
  204. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_cache.py +0 -0
  205. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_cfg.py +0 -0
  206. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_class_keywords_with_targets.py +0 -0
  207. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_cli.py +0 -0
  208. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_closures.py +0 -0
  209. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_code_injection.py +0 -0
  210. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_config_secrets_coverage.py +0 -0
  211. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_constants.py +0 -0
  212. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_correlation.py +0 -0
  213. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_credential_context.py +0 -0
  214. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_dataflow.py +0 -0
  215. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_dependencies.py +0 -0
  216. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_detectors.py +0 -0
  217. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_discovery.py +0 -0
  218. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_django_http_clients.py +0 -0
  219. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_django_routes.py +0 -0
  220. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_django_safe_values.py +0 -0
  221. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_docs.py +0 -0
  222. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_dominance.py +0 -0
  223. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_emit_ir.py +0 -0
  224. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_exceptions.py +0 -0
  225. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_factories_match_loop_else.py +0 -0
  226. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_fail_on.py +0 -0
  227. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_fastapi_cli_precision.py +0 -0
  228. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_frameworks.py +0 -0
  229. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_frontend_gaps.py +0 -0
  230. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_heap.py +0 -0
  231. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_hex_digests.py +0 -0
  232. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_hir_builder.py +0 -0
  233. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_imports.py +0 -0
  234. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_interprocedural.py +0 -0
  235. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_interprocedural_taint.py +0 -0
  236. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_ir.py +0 -0
  237. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_match_patterns.py +0 -0
  238. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_members.py +0 -0
  239. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_module_level.py +0 -0
  240. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_nonlocal_writeback.py +0 -0
  241. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_nosql_injection.py +0 -0
  242. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_open_redirect_destinations.py +0 -0
  243. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_operator_sources.py +0 -0
  244. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_packaging.py +0 -0
  245. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_parallel.py +0 -0
  246. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_parser.py +0 -0
  247. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_plaintext_credentials.py +0 -0
  248. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_plugins.py +0 -0
  249. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_project.py +0 -0
  250. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_project_models.py +0 -0
  251. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_project_validators.py +0 -0
  252. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_provenance.py +0 -0
  253. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_pyir_imports.py +0 -0
  254. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_read_entry_points.py +0 -0
  255. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_real_code_precision.py +0 -0
  256. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_redirect_targets.py +0 -0
  257. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_refutation.py +0 -0
  258. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_refutation_evidence.py +0 -0
  259. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_regression.py +0 -0
  260. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_relative_paths.py +0 -0
  261. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_remaining_limits.py +0 -0
  262. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_reporters.py +0 -0
  263. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_safe_arguments.py +0 -0
  264. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_scale.py +0 -0
  265. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_scopes.py +0 -0
  266. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_secrets_sbom.py +0 -0
  267. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_self_state.py +0 -0
  268. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_source_manager.py +0 -0
  269. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_ssa.py +0 -0
  270. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_ssrf_destinations.py +0 -0
  271. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_ssrf_targets.py +0 -0
  272. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_summary_sanitizers.py +0 -0
  273. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_suppressions.py +0 -0
  274. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_symbol_resolution.py +0 -0
  275. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_symbols.py +0 -0
  276. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_syntactic_rules.py +0 -0
  277. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_syntax_control_flow.py +0 -0
  278. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_syntax_coverage.py +0 -0
  279. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_syntax_expressions.py +0 -0
  280. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_taint.py +0 -0
  281. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_template_context_flows.py +0 -0
  282. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_template_escaping.py +0 -0
  283. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_template_filter_calls.py +0 -0
  284. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_template_request_processor.py +0 -0
  285. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_tornado_bottle_models.py +0 -0
  286. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_url_proof.py +0 -0
  287. {coretrace_python_analyzer-0.16.0 → coretrace_python_analyzer-0.18.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0 (2026-09-28)
4
+
5
+ ### Precision
6
+
7
+ - A Django template file the analyzer cannot read, because of its permissions, no longer counts as escaping everything it renders: it read the file as empty text, and an empty template escapes. The flow through `render_to_string` stays `xss`, and a template extending or including the unreadable one is not established to escape either, as for a template the analyzer cannot find. An unreadable file was already listed among the templates the project names but the run could not read.
8
+
9
+ ### Plugins
10
+
11
+ - An `EntryPoint` may name a project function or class by its own symbol, `python.app.handlers.main`, so an integration reading a deployment declaration, a SAM template or a `serverless.yml`, can make a bare Lambda handler an entry point; the engine itself infers nothing from a function's name. `EntryPoint.inputs` limits the parameters that are input to the given positions, after `self` for a method: the handler's event, not its context (#180).
12
+
13
+ ## 0.17.0 (2026-09-28)
14
+
15
+ ### Plugins
16
+
17
+ - `BasePattern(pattern, label, parameters, kinds)` makes the methods of a class whose base's canonical symbol matches a regular expression entry points, for bases generated per project such as a gRPC servicer's, whatever the module's prefix and however the base is imported. Each parameter after `self` is, by position, either input of the model's kinds or an object of a class the model names, which the sources on that class taint without the object being input: a `Source` on `ServicerContext.invocation_metadata` gives the client's metadata, and `peer()` gives nothing. When the project holds the base, only the methods the base defines are entry points, so a servicer's utility methods are not; otherwise every method with the listed number of parameters is. `async` methods and the messages of an iterated parameter are covered (#176).
18
+
3
19
  ## 0.16.0 (2026-09-27)
4
20
 
5
21
  ### Advisories
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.16.0
3
+ Version: 0.18.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
@@ -151,9 +151,10 @@ class BottleModels(ModelPlugin):
151
151
  | `Source(symbol, label, kinds=ALL)` | A call or attribute whose value is attacker-controlled; `label` names it in messages (`http`, `stdin`, `argv`, `http-response`). `kinds` restricts what it can inject. Text input, such as a form field, a header or a URL parameter, carries `TEXT_KINDS`, every kind but `NOSQL`: a string cannot hold a query operator. |
152
152
  | `Sink(symbol, kinds, positions=(), keywords=())` | A call dangerous for `kinds`. `positions` limits a kind to the given argument indexes, and `keywords` to the given keyword names; a restricted kind reaches only the arguments listed there. SQL sinks read the statement only, and the SSRF sinks of HTTP clients read the URL, by position or as `url=`. A starred argument or `**kwargs` reaches no restricted kind. |
153
153
  | `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`, including when the call is inside a project function the data goes through. |
154
- | `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
154
+ | `EntryPoint(symbol, label, kinds=ALL, inputs=None)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`; or a project function or class named by its own symbol (`python.app.handlers.main`), as an integration reading a deployment declaration names a Lambda handler. `inputs` limits the parameters that are input to those positions, after `self` for a method: a handler's event, not its context. |
155
155
  | `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
156
156
  | `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
157
+ | `BasePattern(pattern, label, parameters=(None,), kinds=ALL)` | A regular expression on the canonical symbol of a class's base, for bases generated per project such as a gRPC servicer's (`(?:^|\.)\w+_pb2_grpc\.\w+Servicer$`), whatever the module's prefix and however the base is imported. The methods taking the parameters listed, by position after `self`, are entry points: a position left `None` is `label` input carrying `kinds`; one holding a class symbol denotes an object of that class, which the sources on the class taint, as `invocation_metadata()` on a `ServicerContext`, without the object itself being input. When the project holds the base, only the methods the base defines count; otherwise every method with that number of parameters. Inheritance identifies a potential service, not its registration on a server. |
157
158
  | `RouteRegistrar(symbol, argument, label, kinds=ALL, keyword=None)` | A call registering a view at argument `argument` (or `keyword`), such as Django's `path`; the view receives `label` input. |
158
159
  | `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
159
160
  | `SafeArgument(symbol, argument, values, position=None, kinds=ALL)` | A call to the sink `symbol` whose `argument` (by keyword, or at `position`) denotes one of `values` is not a sink for `kinds`, such as `yaml.load` with `Loader=yaml.SafeLoader`. Values are written as the analyzer records arguments: a symbol by its canonical name, a constant as Python writes it (`True`). Only a value given explicitly makes the call safe. |
@@ -156,9 +156,9 @@ It establishes escaping only when all of the following hold:
156
156
  - No Python file of the project sets `autoescape` to `False`.
157
157
 
158
158
  The analyzer never assumes escaping where it cannot read the template. That covers a
159
- template named by an expression, a template found through a `DIRS` entry not named
160
- `templates`, one shipped by an installed package, one rendered through `get_template`,
161
- and Flask's Jinja2 templates. The flow is then reported as before. A single-file check
159
+ template file it cannot read, a template named by an expression, a template found
160
+ through a `DIRS` entry not named `templates`, one shipped by an installed package, one
161
+ rendered through `get_template`, and Flask's Jinja2 templates. The flow is then reported as before. A single-file check
162
162
  reads no template.
163
163
 
164
164
  ### Dangerous API usage
@@ -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.16.0"},
244
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.18.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.16.0",
276
+ "name": "coretrace-python-analyzer", "version": "0.18.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:…"}
@@ -513,7 +513,7 @@ project's behalf would go unseen. Without a lock file, a package is never
513
513
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
514
514
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
515
515
  cannot know it. Its `tooling` names the analyzer and each component with its digest
516
- (`coretrace-python-analyzer 0.16.0; curated-advisories 2026.09.25 (sha256:…)`), so the
516
+ (`coretrace-python-analyzer 0.18.0; curated-advisories 2026.09.25 (sha256:…)`), so the
517
517
  `@id` changes with the advisory data a statement was decided with.
518
518
 
519
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.16.0"
7
+ version = "0.18.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.16.0"
3
+ __version__ = "0.18.0"
4
4
 
@@ -80,6 +80,7 @@ from coretrace_python.interprocedural import (
80
80
  ModuleFunction,
81
81
  ModuleGraph,
82
82
  ProjectSummaries,
83
+ SignaturesAnalysis,
83
84
  SummaryAnalysis,
84
85
  SummaryIndex,
85
86
  SymbolRead,
@@ -164,6 +165,7 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
164
165
  EscapedTemplates,
165
166
  ClearingAnalysis,
166
167
  TemplateCallsAnalysis,
168
+ SignaturesAnalysis,
167
169
  )
168
170
 
169
171
 
@@ -205,6 +207,7 @@ class ResultsEvicted(TransformationPass):
205
207
  EscapedTemplates,
206
208
  ClearingAnalysis,
207
209
  TemplateCallsAnalysis,
210
+ SignaturesAnalysis,
208
211
  }
209
212
  )
210
213
 
@@ -268,6 +271,7 @@ def build_manager(
268
271
  table = (models or SecurityModelRegistry()).freeze()
269
272
  manager.provide(SecurityModelAnalysis, table)
270
273
  manager.provide(MembersAnalysis, table.members_by_class())
274
+ manager.provide(SignaturesAnalysis, table.signatures())
271
275
  manager.provide(ClearingAnalysis, table.clearing())
272
276
  manager.provide(ProjectSummaries, SummaryIndex())
273
277
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -323,6 +327,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
323
327
  table = plugin_models(loaded.plugin for loaded in registry)
324
328
  manager.provide(SecurityModelAnalysis, table)
325
329
  manager.provide(MembersAnalysis, table.members_by_class())
330
+ manager.provide(SignaturesAnalysis, table.signatures())
326
331
  manager.provide(ClearingAnalysis, table.clearing())
327
332
  manager.provide(ProjectSummaries, SummaryIndex())
328
333
  manager.provide(RegisteredRoutes, _routes_of(manager))
@@ -418,6 +423,7 @@ def analyze_project(
418
423
  for manager in managers.values():
419
424
  manager.provide(SecurityModelAnalysis, models)
420
425
  manager.provide(MembersAnalysis, members)
426
+ manager.provide(SignaturesAnalysis, models.signatures())
421
427
  manager.provide(DependencyAnalysis, dependencies)
422
428
 
423
429
  imports: dict[str, ImportTable] = {}
@@ -730,6 +736,7 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
730
736
  for manager in managers.values():
731
737
  manager.provide(SecurityModelAnalysis, models)
732
738
  manager.provide(MembersAnalysis, members)
739
+ manager.provide(SignaturesAnalysis, models.signatures())
733
740
  manager.provide(DependencyAnalysis, dependencies)
734
741
  manager.provide(RegisteredRoutes, routes)
735
742
  manager.provide(EscapedTemplates, escaped)
@@ -8,6 +8,8 @@ from coretrace_python.interprocedural.callgraph import (
8
8
  ExternalSymbol,
9
9
  KnownFunction,
10
10
  ModuleFunction,
11
+ Signature,
12
+ SignaturesAnalysis,
11
13
  SymbolRead,
12
14
  Target,
13
15
  UnknownTarget,
@@ -56,6 +58,8 @@ __all__ = [
56
58
  "Mutation",
57
59
  "NonlocalWrite",
58
60
  "ProjectSummaries",
61
+ "Signature",
62
+ "SignaturesAnalysis",
59
63
  "SummaryAnalysis",
60
64
  "SummaryIndex",
61
65
  "SummaryTable",
@@ -9,6 +9,7 @@ reads, called or not: reading ``request.form`` runs its getter.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import re
12
13
  from collections.abc import Mapping
13
14
  from dataclasses import dataclass, field
14
15
  from types import MappingProxyType
@@ -104,6 +105,30 @@ class SymbolRead:
104
105
  symbol: SymbolId
105
106
 
106
107
 
108
+ @dataclass(frozen=True)
109
+ class Signature:
110
+ """What the parameters of the methods of a class denote when the canonical symbol
111
+ of its base matches ``pattern``, by position after ``self``: a class symbol, or None
112
+ for a parameter denoting none. A method taking another number of parameters has
113
+ none. The engine derives signatures from the security models (§16)."""
114
+
115
+ pattern: str
116
+ parameters: tuple[SymbolId | None, ...]
117
+
118
+ def matches(self, base: SymbolId) -> bool:
119
+ return re.search(self.pattern, base.canonical_name) is not None
120
+
121
+
122
+ class SignaturesAnalysis(Analysis[tuple[Signature, ...]]):
123
+ """The signatures the models declare, provided by the engine; none on its own."""
124
+
125
+ name: ClassVar[str] = "interprocedural.signatures"
126
+
127
+ @classmethod
128
+ def compute(cls, ctx: AnalysisContext) -> tuple[Signature, ...]:
129
+ return ()
130
+
131
+
107
132
  @dataclass(frozen=True)
108
133
  class ModuleFunction:
109
134
  """One function of a module as a project plugin sees it: the name the call graph
@@ -357,6 +382,20 @@ def _annotated(
357
382
  return found
358
383
 
359
384
 
385
+ def _declared(
386
+ function: nodes.Function, ssa: FunctionIR, bases: tuple[SymbolId, ...], signatures: tuple[Signature, ...]
387
+ ) -> dict[Value, SymbolId]:
388
+ """Parameters a signature types by their position, for a method of a class one of
389
+ whose ``bases`` matches the signature and which takes the parameters it lists."""
390
+
391
+ given = function.parameters[1:]
392
+ for signature in signatures:
393
+ if any(signature.matches(base) for base in bases) and len(given) == len(signature.parameters):
394
+ typed = zip(ssa.parameters[1:], signature.parameters, strict=False)
395
+ return {value: symbol for value, symbol in typed if symbol is not None}
396
+ return {}
397
+
398
+
360
399
  def _typed_with_module_classes(
361
400
  function: nodes.Function, ssa: FunctionIR, classes: Mapping[str, frozenset[str]]
362
401
  ) -> dict[Value, str]:
@@ -372,12 +411,15 @@ def _typed_with_module_classes(
372
411
 
373
412
  class CallGraphAnalysis(Analysis[CallGraph]):
374
413
  name: ClassVar[str] = "interprocedural.callgraph"
375
- requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({SSAAnalysis, ScopeAnalysis, SymbolAnalysis})
414
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset(
415
+ {SSAAnalysis, ScopeAnalysis, SymbolAnalysis, SignaturesAnalysis}
416
+ )
376
417
 
377
418
  @classmethod
378
419
  def compute(cls, ctx: AnalysisContext) -> CallGraph:
379
420
  scopes = ctx.get(ScopeAnalysis)
380
421
  table = ctx.get(SymbolAnalysis)
422
+ signatures = ctx.get(SignaturesAnalysis)
381
423
  definitions: dict[str, nodes.Function] = {}
382
424
  for function in analyzable_functions(ctx.module):
383
425
  # A property and its setter, or a redefinition, share a qualified name; each
@@ -396,14 +438,15 @@ class CallGraphAnalysis(Analysis[CallGraph]):
396
438
  for statement in ctx.module.body
397
439
  if isinstance(statement, nodes.Class)
398
440
  }
441
+ # Inheritance follows the first base that resolves; a signature may match any.
399
442
  bases: dict[str, SymbolId] = {}
443
+ ancestry: dict[str, tuple[SymbolId, ...]] = {}
400
444
  for statement in ctx.module.body:
401
445
  if isinstance(statement, nodes.Class):
402
- for base_expression in statement.bases:
403
- symbol = table.resolve_expression(scopes.module_scope.id, base_expression)
404
- if symbol is not None:
405
- bases[statement.name] = symbol
406
- break
446
+ resolved = [table.resolve_expression(scopes.module_scope.id, base) for base in statement.bases]
447
+ ancestry[statement.name] = tuple(symbol for symbol in resolved if symbol is not None)
448
+ if ancestry[statement.name]:
449
+ bases[statement.name] = ancestry[statement.name][0]
407
450
  sites: dict[str, tuple[CallSite, ...]] = {}
408
451
  symbols: dict[str, Mapping[Value, SymbolId]] = {}
409
452
  reads: dict[str, tuple[SymbolRead, ...]] = {}
@@ -418,11 +461,13 @@ class CallGraphAnalysis(Analysis[CallGraph]):
418
461
  owner = name.rsplit(".", 1)[0] if "." in name and name.rsplit(".", 1)[0] in classes else None
419
462
  if owner is not None and _is_staticmethod(function):
420
463
  owner = None # a static method has no receiver
464
+ # A signature types parameters by their position; an annotation says more.
465
+ declared = _declared(function, ssa, ancestry.get(owner, ()) if owner is not None else (), signatures)
421
466
  targets, symbols[name] = resolve_targets(
422
467
  ssa,
423
468
  scopes,
424
469
  known,
425
- _annotated(function, ssa, scopes, table),
470
+ {**declared, **_annotated(function, ssa, scopes, table)},
426
471
  frozenset(definitions),
427
472
  classes,
428
473
  owner,
@@ -363,6 +363,12 @@ class SummaryIndex:
363
363
  def summary(self, symbol: SymbolId) -> FunctionSummary | None:
364
364
  return self.summaries.get(symbol)
365
365
 
366
+ def defines(self, owner: SymbolId) -> bool:
367
+ """Whether the project defines a function of ``owner``, a class or a module."""
368
+
369
+ prefix = owner.canonical_name + "."
370
+ return any(s.canonical_name.startswith(prefix) for s in self.symbols)
371
+
366
372
  def __eq__(self, other: object) -> bool:
367
373
  return isinstance(other, SummaryIndex) and dict(self.summaries) == dict(other.summaries)
368
374
 
@@ -12,6 +12,7 @@ from coretrace_python.taint.engine import (
12
12
  from coretrace_python.taint.models import (
13
13
  TEXT_KINDS,
14
14
  AuthorizationGuard,
15
+ BasePattern,
15
16
  EntryPoint,
16
17
  Model,
17
18
  ModelError,
@@ -47,6 +48,7 @@ __all__ = [
47
48
  "FILTER_FUNCTIONS",
48
49
  "TEXT_KINDS",
49
50
  "AuthorizationGuard",
51
+ "BasePattern",
50
52
  "EntryPoint",
51
53
  "EntryPointAnalysis",
52
54
  "EscapedTemplates",
@@ -80,6 +80,7 @@ from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId, SymbolTa
80
80
  from coretrace_python.source import SourceSpan
81
81
  from coretrace_python.taint.models import (
82
82
  TEXT_KINDS,
83
+ BasePattern,
83
84
  EntryPoint,
84
85
  ModelTable,
85
86
  SecurityModelAnalysis,
@@ -780,9 +781,11 @@ def entry_point_of(
780
781
  symbols: SymbolTable,
781
782
  owner: nodes.Class | None = None,
782
783
  instances: Instances | None = None,
784
+ project: SummaryIndex | None = None,
783
785
  ) -> EntryPoint | None:
784
786
  """The entry-point model matching one of the function's decorators or, for a
785
- method, one of the bases of ``owner``, if any."""
787
+ method, one of the bases of ``owner``, if any: by symbol, or by a base pattern when
788
+ the method is one the pattern covers (``project`` says what the base defines)."""
786
789
 
787
790
  scope = scopes.scope_for(function)
788
791
  enclosing = scope.parent if scope.parent is not None else scope.id
@@ -803,9 +806,24 @@ def entry_point_of(
803
806
  entry = models.entry_point(candidate)
804
807
  if entry is not None:
805
808
  return entry
809
+ pattern = models.base_pattern(candidate) if id(expression) in enclosing_of else None
810
+ if pattern is not None and _covered(function, candidate, pattern, project):
811
+ return EntryPoint(candidate, pattern.label, pattern.kinds)
806
812
  return None
807
813
 
808
814
 
815
+ def _covered(function: nodes.Function, base: SymbolId, pattern: BasePattern, project: SummaryIndex | None) -> bool:
816
+ """Whether a method of a class deriving from ``base`` is one ``pattern`` covers: it
817
+ takes the parameters the pattern lists and, when the project holds the base, the
818
+ base defines it; a utility method of the class is no entry point."""
819
+
820
+ if len(function.parameters) - 1 != len(pattern.parameters):
821
+ return False
822
+ if project is None or not project.defines(base):
823
+ return True
824
+ return project.summary(base.attribute(function.name)) is not None
825
+
826
+
809
827
  def function_entry_point(
810
828
  function: nodes.Function,
811
829
  module: nodes.Module,
@@ -814,24 +832,34 @@ def function_entry_point(
814
832
  symbols: SymbolTable,
815
833
  instances: Instances | None = None,
816
834
  routes: Routes | None = None,
835
+ project: SummaryIndex | None = None,
817
836
  ) -> EntryPoint | None:
818
837
  """The entry point ``function`` is: one of its decorators or, for a method, one of its
819
- class's bases matching an entry-point model, or a registration elsewhere (``routes``)."""
838
+ class's bases matching an entry-point model, a registration elsewhere (``routes``), or
839
+ a model naming the function, or its class, by its own project symbol."""
820
840
 
821
841
  owner = _owner(module, function)
822
842
  enclosing_function = _enclosing(module, function)
823
843
  if enclosing_function is not None:
824
844
  # ``app = Flask(__name__)`` inside ``create_app``: routes defined there resolve.
825
845
  instances = {**(instances or {}), **local_instances(enclosing_function, scopes, symbols)}
826
- entry = entry_point_of(function, models, scopes, symbols, owner, instances)
827
- if entry is None and routes:
846
+ entry = entry_point_of(function, models, scopes, symbols, owner, instances, project)
847
+ if entry is None:
828
848
  qualified = function.name if owner is None else f"{owner.name}.{function.name}"
829
- entry = routes.get(project_symbol(module.name, qualified))
849
+ entry = _declared(project_symbol(module.name, qualified), models, routes)
830
850
  if entry is None and owner is not None:
831
- entry = routes.get(project_symbol(module.name, owner.name))
851
+ entry = _declared(project_symbol(module.name, owner.name), models, routes)
832
852
  return entry
833
853
 
834
854
 
855
+ def _declared(symbol: SymbolId, models: ModelTable, routes: Routes | None) -> EntryPoint | None:
856
+ """The entry point a project ``symbol`` is: registered as a handler, or named by a
857
+ model."""
858
+
859
+ registered = routes.get(symbol) if routes else None
860
+ return registered if registered is not None else models.entry_point(symbol)
861
+
862
+
835
863
  def _owner(module: nodes.Module, function: nodes.Function) -> nodes.Class | None:
836
864
  """The top-level class ``function`` is a method of, if any."""
837
865
 
@@ -849,23 +877,31 @@ def parameter_sources(
849
877
  symbols: SymbolTable,
850
878
  instances: Instances | None = None,
851
879
  routes: Routes | None = None,
880
+ project: SummaryIndex | None = None,
852
881
  ) -> Mapping[int, Source]:
853
882
  """The attacker-controlled parameters of ``function``, by index: every parameter of an
854
883
  entry point (``self`` excepted for a method), including one registered elsewhere
855
884
  (``routes``), and every parameter annotated with a typed-parameter symbol. An entry
856
885
  point's parameter annotated with scalars is text: FastAPI validates it before the
857
886
  handler runs, and a scalar cannot hold a query operator. So are the URL parameters
858
- a view receives after a request object."""
887
+ a view receives after a request object. A parameter a base pattern types with a
888
+ class is not input: the sources on that class say what it gives."""
859
889
 
860
890
  owner = _owner(module, function)
861
891
  sources: dict[int, Source] = {}
862
892
  scope = scopes.scope_for(function)
863
893
  enclosing = scope.parent if scope.parent is not None else scope.id
864
- entry = function_entry_point(function, module, models, scopes, symbols, instances, routes)
894
+ entry = function_entry_point(function, module, models, scopes, symbols, instances, routes, project)
865
895
  if entry is not None:
866
896
  first = 1 if owner is not None else 0
867
897
  request = models.request_object(entry.symbol)
898
+ pattern = models.base_pattern(entry.symbol) if models.entry_point(entry.symbol) is None else None
899
+ roles = pattern.parameters if pattern is not None else ()
868
900
  for index in range(first, len(function.parameters)):
901
+ if index - first < len(roles) and roles[index - first] is not None:
902
+ continue
903
+ if entry.inputs is not None and index - first not in entry.inputs:
904
+ continue
869
905
  annotation = function.parameters[index].annotation
870
906
  text = (request is not None and index > first) or (
871
907
  annotation is not None and _scalar(annotation, symbols, enclosing)
@@ -1010,11 +1046,12 @@ class TaintAnalysis(FunctionAnalysis[TaintFacts]):
1010
1046
  graph = ctx.get(CallGraphAnalysis)
1011
1047
  models = ctx.get(SecurityModelAnalysis)
1012
1048
  scopes, symbols = ctx.get(ScopeAnalysis), ctx.get(SymbolAnalysis)
1013
- instances = factory_instances(ctx.module, scopes, symbols, ctx.get(SummaryAnalysis), ctx.get(ProjectSummaries))
1049
+ project = ctx.get(ProjectSummaries)
1050
+ instances = factory_instances(ctx.module, scopes, symbols, ctx.get(SummaryAnalysis), project)
1014
1051
  routes = ctx.get(RegisteredRoutes)
1015
1052
 
1016
1053
  def sources_of(member: nodes.Function) -> Mapping[int, Source]:
1017
- return parameter_sources(member, ctx.module, models, scopes, symbols, instances, routes)
1054
+ return parameter_sources(member, ctx.module, models, scopes, symbols, instances, routes, project)
1018
1055
 
1019
1056
  ssa = ctx.get(SSAAnalysis, function)
1020
1057
  heap = ctx.get(HeapAnalysis, function)
@@ -1068,9 +1105,10 @@ class EntryPointAnalysis(Analysis[tuple[ModuleFunction, ...]]):
1068
1105
  scopes, symbols = ctx.get(ScopeAnalysis), ctx.get(SymbolAnalysis)
1069
1106
  instances = factory_instances(ctx.module, scopes, symbols, ctx.get(SummaryAnalysis), ctx.get(ProjectSummaries))
1070
1107
  routes = ctx.get(RegisteredRoutes)
1108
+ project = ctx.get(ProjectSummaries)
1071
1109
  functions: list[ModuleFunction] = []
1072
1110
  for name, function in graph.definitions.items():
1073
- entry = function_entry_point(function, ctx.module, models, scopes, symbols, instances, routes)
1111
+ entry = function_entry_point(function, ctx.module, models, scopes, symbols, instances, routes, project)
1074
1112
  functions.append(ModuleFunction(name, function.span, entry.label if entry is not None else None))
1075
1113
  return tuple(functions)
1076
1114
 
@@ -17,7 +17,7 @@ from types import MappingProxyType
17
17
  from typing import ClassVar
18
18
 
19
19
  from coretrace_python.analysis import Analysis, AnalysisContext, MissingInputError
20
- from coretrace_python.interprocedural import Clearing, TemplateCalls, TemplateFilter
20
+ from coretrace_python.interprocedural import Clearing, Signature, TemplateCalls, TemplateFilter
21
21
  from coretrace_python.semantic.symbols import Members, SymbolId
22
22
 
23
23
 
@@ -110,12 +110,17 @@ class Sanitizer:
110
110
 
111
111
  @dataclass(frozen=True)
112
112
  class EntryPoint:
113
- """Functions decorated by ``symbol``, and methods of classes deriving from it,
114
- receive attacker-controlled parameters."""
113
+ """Functions decorated by ``symbol``, methods of classes deriving from it, and, when
114
+ ``symbol`` names a project function or class, that function or the class's methods
115
+ receive attacker-controlled parameters: those at ``inputs``, by position after
116
+ ``self`` for a method, or every parameter when ``inputs`` is None. A deployment
117
+ declaration an integration reads names a Lambda handler this way, whose event is
118
+ input and whose context is not."""
115
119
 
116
120
  symbol: SymbolId
117
121
  label: str
118
122
  kinds: TaintKind = TaintKind.ALL
123
+ inputs: tuple[int, ...] | None = None
119
124
 
120
125
 
121
126
  @dataclass(frozen=True)
@@ -145,6 +150,32 @@ class NamedParameter:
145
150
  return re.search(self.pattern, name) is not None
146
151
 
147
152
 
153
+ @dataclass(frozen=True)
154
+ class BasePattern:
155
+ """Methods of a class whose base's canonical symbol matches ``pattern`` are entry
156
+ points labelled ``label``, when they take the parameters the model lists, by
157
+ position after ``self``: a position left None is attacker input carrying ``kinds``;
158
+ one holding a class symbol denotes an object of that class, which the sources on
159
+ the class taint. A gRPC servicer's RPCs take the request, or the iterator of the
160
+ client's messages, and a ``ServicerContext``. When the project holds the base,
161
+ only the methods it defines are entry points; otherwise every method with that
162
+ number of parameters. Inheritance identifies a potential service, not its
163
+ registration on a server."""
164
+
165
+ pattern: str
166
+ label: str
167
+ parameters: tuple[SymbolId | None, ...] = (None,)
168
+ kinds: TaintKind = TaintKind.ALL
169
+
170
+ @property
171
+ def symbol(self) -> SymbolId:
172
+ digest = hashlib.sha1(self.pattern.encode("utf-8")).hexdigest()[:12]
173
+ return SymbolId(f"python.base.p{digest}")
174
+
175
+ def matches(self, base: SymbolId) -> bool:
176
+ return re.search(self.pattern, base.canonical_name) is not None
177
+
178
+
148
179
  @dataclass(frozen=True)
149
180
  class RouteRegistrar:
150
181
  """A call registering a handler elsewhere (``path('login/', views.log_in)``): the
@@ -251,6 +282,7 @@ Model = (
251
282
  | TemplateRender
252
283
  | Members
253
284
  | RequestObject
285
+ | BasePattern
254
286
  )
255
287
 
256
288
 
@@ -270,6 +302,7 @@ class ModelTable:
270
302
  template_renders: tuple[TemplateRender, ...] = ()
271
303
  members: tuple[Members, ...] = ()
272
304
  request_objects: tuple[RequestObject, ...] = ()
305
+ base_patterns: tuple[BasePattern, ...] = ()
273
306
  _by_symbol: dict[type[Model], dict[SymbolId, Model]] = field(
274
307
  init=False, repr=False, compare=False
275
308
  )
@@ -287,6 +320,7 @@ class ModelTable:
287
320
  SafeArgument: {m.symbol: m for m in self.safe_arguments},
288
321
  TemplateRender: {m.symbol: m for m in self.template_renders},
289
322
  RequestObject: {m.symbol: m for m in self.request_objects},
323
+ BasePattern: {m.symbol: m for m in self.base_patterns},
290
324
  }
291
325
  object.__setattr__(self, "_by_symbol", MappingProxyType(index))
292
326
 
@@ -356,8 +390,20 @@ class ModelTable:
356
390
  self.template_renders,
357
391
  self.members,
358
392
  self.request_objects,
393
+ self.base_patterns,
359
394
  )
360
395
 
396
+ def base_pattern(self, base: SymbolId) -> BasePattern | None:
397
+ """The pattern model the canonical symbol of a class's ``base`` matches, if any."""
398
+
399
+ return next((m for m in self.base_patterns if m.matches(base)), None)
400
+
401
+ def signatures(self) -> tuple[Signature, ...]:
402
+ """What the methods of the classes the base patterns match receive, by position,
403
+ for the call graph to type them."""
404
+
405
+ return tuple(Signature(m.pattern, m.parameters) for m in self.base_patterns)
406
+
361
407
  def sanitizer(self, symbol: SymbolId) -> Sanitizer | None:
362
408
  found = self._by_symbol[Sanitizer].get(symbol)
363
409
  return found if isinstance(found, Sanitizer) else None
@@ -451,6 +497,7 @@ class SecurityModelRegistry:
451
497
  template_renders=tuple(m for m in models if isinstance(m, TemplateRender)),
452
498
  members=tuple(m for m in models if isinstance(m, Members)),
453
499
  request_objects=tuple(m for m in models if isinstance(m, RequestObject)),
500
+ base_patterns=tuple(m for m in models if isinstance(m, BasePattern)),
454
501
  )
455
502
 
456
503
 
@@ -15,7 +15,8 @@ it extends or includes, can output data unescaped:
15
15
 
16
16
  Templates are found under the project's ``templates`` directories, by their path below
17
17
  one. A template the engine cannot find — named by an expression, under a ``DIRS`` entry
18
- of another name, shipped by an installed package — is never assumed to escape.
18
+ of another name, shipped by an installed package — or cannot read is never assumed to
19
+ escape.
19
20
 
20
21
  A template applying a filter of Django's own libraries calls the function behind it
21
22
  whenever it is rendered, though no Python call names that function: ``{{ bio|striptags }}``
@@ -149,7 +150,7 @@ def escaped_templates(root: Path) -> frozenset[str]:
149
150
  local: dict[str, bool] = {}
150
151
  requires: dict[str, set[str]] = {}
151
152
  for name, paths in found.items():
152
- verdicts = [_inspect(_read(path)) for path in paths]
153
+ verdicts = [_verdict(path) for path in paths]
153
154
  local[name] = all(safe for safe, _ in verdicts)
154
155
  requires[name] = {needed for _, needs in verdicts for needed in needs}
155
156
  escaped = {name for name, safe in local.items() if safe}
@@ -171,6 +172,14 @@ def _read(path: Path) -> str:
171
172
  return _text(path) or ""
172
173
 
173
174
 
175
+ def _verdict(path: Path) -> tuple[bool, set[str]]:
176
+ """Whether one template file escapes all it prints, and what it needs: a file the
177
+ engine cannot read is not established to escape, like a template it cannot find."""
178
+
179
+ text = _text(path)
180
+ return (False, set()) if text is None else _inspect(text)
181
+
182
+
174
183
  def _text(path: Path) -> str | None:
175
184
  """The text of ``path``, or None when it cannot be read."""
176
185