coretrace-python-analyzer 0.17.0__tar.gz → 0.19.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 (293) hide show
  1. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/CHANGELOG.md +27 -0
  2. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/docs/plugins.md +16 -7
  4. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/docs/usage.md +54 -16
  5. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cache.py +9 -4
  8. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/engine.py +95 -28
  9. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/coverage.py +6 -4
  10. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/interprocedural/__init__.py +2 -0
  11. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/interprocedural/callgraph.py +6 -2
  12. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/interprocedural/modulegraph.py +103 -9
  13. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/semantic/scopes.py +2 -2
  14. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/engine.py +64 -8
  15. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/models.py +7 -2
  16. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/templates.py +290 -31
  17. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/DjangoGoat.json +28 -4
  18. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/httpie.json +16 -4
  19. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/pygoat.json +35 -4
  20. coretrace_python_analyzer-0.19.0/tests/test_block_super.py +155 -0
  21. coretrace_python_analyzer-0.19.0/tests/test_declared_entry_points.py +155 -0
  22. coretrace_python_analyzer-0.19.0/tests/test_entry_point_aliases.py +160 -0
  23. coretrace_python_analyzer-0.19.0/tests/test_function_aliases_api.py +84 -0
  24. coretrace_python_analyzer-0.19.0/tests/test_module_identity.py +384 -0
  25. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_release.py +1 -1
  26. coretrace_python_analyzer-0.19.0/tests/test_settings_scope.py +109 -0
  27. coretrace_python_analyzer-0.19.0/tests/test_template_filter_overrides.py +385 -0
  28. coretrace_python_analyzer-0.19.0/tests/test_unreadable_templates.py +70 -0
  29. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/.github/workflows/ci.yml +0 -0
  30. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/.github/workflows/release.yml +0 -0
  31. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/.gitignore +0 -0
  32. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/LICENSE +0 -0
  33. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/NOTICE +0 -0
  34. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/README.md +0 -0
  35. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/docs/architecture.md +0 -0
  36. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/examples/add.py +0 -0
  37. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/examples/canonical_symbols.py +0 -0
  38. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/__main__.py +0 -0
  39. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/__init__.py +0 -0
  40. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/constants.py +0 -0
  41. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/heap.py +0 -0
  42. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/ranges.py +0 -0
  43. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/strings.py +0 -0
  44. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/abstract/values.py +0 -0
  45. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/analysis/__init__.py +0 -0
  46. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/analysis/manager.py +0 -0
  47. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/analysis/provider.py +0 -0
  48. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  49. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  50. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  51. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  52. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  53. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  54. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  55. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  56. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  57. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  59. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  60. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  61. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  62. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  63. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  64. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  65. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  66. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  67. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  68. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  69. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  70. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  71. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  72. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  73. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  74. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  75. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  76. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  77. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  78. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  79. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  80. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  81. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  83. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  84. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/code_injection/code_injection.py +0 -0
  85. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/code_injection/plugin.toml +0 -0
  86. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  87. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  88. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  89. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  90. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/nosql_injection/nosql_injection.py +0 -0
  91. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/nosql_injection/plugin.toml +0 -0
  92. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  93. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  94. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  95. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  97. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  99. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  100. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  101. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  102. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  103. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  104. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  105. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  106. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  107. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  108. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  109. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  110. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  111. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  112. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  113. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  114. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  115. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  116. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  117. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  118. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  119. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  120. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  121. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  122. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cfg/__init__.py +0 -0
  123. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cfg/builder.py +0 -0
  124. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cfg/dominance.py +0 -0
  125. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cfg/model.py +0 -0
  126. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/cli.py +0 -0
  127. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  128. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  129. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dataflow/solver.py +0 -0
  130. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/__init__.py +0 -0
  131. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/advisories.py +0 -0
  132. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/correlation.py +0 -0
  133. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/graph.py +0 -0
  134. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/policy.py +0 -0
  135. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/sbom.py +0 -0
  136. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/dependency/vex.py +0 -0
  137. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/__init__.py +0 -0
  138. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/baseline.py +0 -0
  139. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/model.py +0 -0
  140. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/provenance.py +0 -0
  141. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/refutation.py +0 -0
  142. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/findings/suppressions.py +0 -0
  143. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/frontend/__init__.py +0 -0
  144. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  145. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/frontend/parser.py +0 -0
  146. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/hir/__init__.py +0 -0
  147. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/hir/nodes.py +0 -0
  148. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/hir/visitors.py +0 -0
  149. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
  150. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/__init__.py +0 -0
  151. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/defuse.py +0 -0
  152. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/lowering.py +0 -0
  153. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/model.py +0 -0
  154. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/printer.py +0 -0
  155. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/ir/ssa.py +0 -0
  156. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/__init__.py +0 -0
  157. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/api.py +0 -0
  158. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/detectors.py +0 -0
  159. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/loader.py +0 -0
  160. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/manifest.py +0 -0
  161. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/registry.py +0 -0
  162. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/plugins/secrets.py +0 -0
  163. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/reporters/__init__.py +0 -0
  164. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/reporters/json_format.py +0 -0
  165. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/reporters/report.py +0 -0
  166. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/reporters/sarif.py +0 -0
  167. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/reporters/text.py +0 -0
  168. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/semantic/__init__.py +0 -0
  169. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/semantic/identity.py +0 -0
  170. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/semantic/imports.py +0 -0
  171. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/semantic/symbols.py +0 -0
  172. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/source/__init__.py +0 -0
  173. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/source/manager.py +0 -0
  174. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/source/model.py +0 -0
  175. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/__init__.py +0 -0
  176. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/routes.py +0 -0
  177. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/src/coretrace_python/taint/urls.py +0 -0
  178. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/Chatbot.json +0 -0
  179. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  180. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/InsecureGram.json +0 -0
  181. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  182. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/NPL-IA.json +0 -0
  183. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  184. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  185. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/dvpwa.json +0 -0
  186. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  187. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/healthchecks.json +0 -0
  188. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/luigi.json +0 -0
  189. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/my_gdb.json +0 -0
  190. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/network-topologer.json +0 -0
  191. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  192. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  193. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/expected/yt-fts.json +0 -0
  194. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  195. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  196. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  197. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  198. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/regression/repositories.toml +0 -0
  199. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_advisory_evidence.py +0 -0
  200. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_advisory_feeds.py +0 -0
  201. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_advisory_url_host.py +0 -0
  202. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_aiohttp_models.py +0 -0
  203. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_analysis_manager.py +0 -0
  204. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_architecture.py +0 -0
  205. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_argument_conditions.py +0 -0
  206. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_attacker_arguments.py +0 -0
  207. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_audit_detectors.py +0 -0
  208. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_base_patterns.py +0 -0
  209. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_baseline.py +0 -0
  210. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_cache.py +0 -0
  211. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_cfg.py +0 -0
  212. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_class_keywords_with_targets.py +0 -0
  213. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_cli.py +0 -0
  214. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_closures.py +0 -0
  215. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_code_injection.py +0 -0
  216. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_config_secrets_coverage.py +0 -0
  217. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_constants.py +0 -0
  218. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_correlation.py +0 -0
  219. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_credential_context.py +0 -0
  220. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_dataflow.py +0 -0
  221. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_dependencies.py +0 -0
  222. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_detectors.py +0 -0
  223. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_discovery.py +0 -0
  224. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_django_http_clients.py +0 -0
  225. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_django_routes.py +0 -0
  226. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_django_safe_values.py +0 -0
  227. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_docs.py +0 -0
  228. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_dominance.py +0 -0
  229. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_emit_ir.py +0 -0
  230. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_exceptions.py +0 -0
  231. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_factories_match_loop_else.py +0 -0
  232. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_fail_on.py +0 -0
  233. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_fastapi_cli_precision.py +0 -0
  234. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_frameworks.py +0 -0
  235. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_frontend_gaps.py +0 -0
  236. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_heap.py +0 -0
  237. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_hex_digests.py +0 -0
  238. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_hir_builder.py +0 -0
  239. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_imports.py +0 -0
  240. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_interprocedural.py +0 -0
  241. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_interprocedural_taint.py +0 -0
  242. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_ir.py +0 -0
  243. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_match_patterns.py +0 -0
  244. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_members.py +0 -0
  245. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_module_level.py +0 -0
  246. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_nonlocal_writeback.py +0 -0
  247. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_nosql_injection.py +0 -0
  248. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_open_redirect_destinations.py +0 -0
  249. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_operator_sources.py +0 -0
  250. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_packaging.py +0 -0
  251. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_parallel.py +0 -0
  252. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_parser.py +0 -0
  253. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_plaintext_credentials.py +0 -0
  254. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_plugins.py +0 -0
  255. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_project.py +0 -0
  256. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_project_models.py +0 -0
  257. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_project_validators.py +0 -0
  258. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_provenance.py +0 -0
  259. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_pyir_imports.py +0 -0
  260. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_read_entry_points.py +0 -0
  261. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_real_code_precision.py +0 -0
  262. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_redirect_targets.py +0 -0
  263. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_refutation.py +0 -0
  264. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_refutation_evidence.py +0 -0
  265. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_regression.py +0 -0
  266. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_relative_paths.py +0 -0
  267. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_remaining_limits.py +0 -0
  268. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_reporters.py +0 -0
  269. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_safe_arguments.py +0 -0
  270. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_scale.py +0 -0
  271. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_scopes.py +0 -0
  272. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_secrets_sbom.py +0 -0
  273. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_self_state.py +0 -0
  274. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_source_manager.py +0 -0
  275. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_ssa.py +0 -0
  276. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_ssrf_destinations.py +0 -0
  277. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_ssrf_targets.py +0 -0
  278. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_summary_sanitizers.py +0 -0
  279. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_suppressions.py +0 -0
  280. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_symbol_resolution.py +0 -0
  281. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_symbols.py +0 -0
  282. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_syntactic_rules.py +0 -0
  283. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_syntax_control_flow.py +0 -0
  284. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_syntax_coverage.py +0 -0
  285. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_syntax_expressions.py +0 -0
  286. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_taint.py +0 -0
  287. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_template_context_flows.py +0 -0
  288. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_template_escaping.py +0 -0
  289. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_template_filter_calls.py +0 -0
  290. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_template_request_processor.py +0 -0
  291. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_tornado_bottle_models.py +0 -0
  292. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_url_proof.py +0 -0
  293. {coretrace_python_analyzer-0.17.0 → coretrace_python_analyzer-0.19.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.0 (2026-09-29)
4
+
5
+ ### Analysis
6
+
7
+ - 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).
8
+
9
+ ### Precision
10
+
11
+ - 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).
12
+ - 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).
13
+ - 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).
14
+
15
+ ### Plugins
16
+
17
+ - `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.
18
+ - 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.
19
+
20
+ ## 0.18.0 (2026-09-28)
21
+
22
+ ### Precision
23
+
24
+ - 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.
25
+
26
+ ### Plugins
27
+
28
+ - 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).
29
+
3
30
  ## 0.17.0 (2026-09-28)
4
31
 
5
32
  ### Plugins
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.17.0
3
+ Version: 0.19.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
 
@@ -151,7 +155,7 @@ class BottleModels(ModelPlugin):
151
155
  | `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
156
  | `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
157
  | `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`. |
158
+ | `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
159
  | `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
156
160
  | `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
157
161
  | `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. |
@@ -255,14 +259,19 @@ module graph, the dependency graph, every advisory the plugins and advisory file
255
259
  contributed, the policy, the project root, and each module's imports, call graph and
256
260
  functions. `ctx.functions(module)` gives every function as the call graph names it
257
261
  (`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
262
+ one, the label of the entry point it is (`http` for a route, `argv` for a command)
263
+ when the models make it one, and its `aliases`, the module-level names bound to it by
264
+ assigning one name to another (`main = actual`), transitively and in source order, so
265
+ an integration declaring a handler by the name a module binds can tell a name the
266
+ engine resolves to no function; a method or a nested function has none. `ctx.call_graph(module)` gives each function's call sites
260
267
  (`sites(function)`) and the symbols it reads, called or not (`reads(function)`: reading
261
268
  `request.form['name']` reads `python.flask.request.form`), each where it first reads it.
262
269
  `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`).
270
+ their `names`, the filters of Django's own libraries they apply as `calls` (each a
271
+ `FilterCall(symbol, span)`, the function behind the filter and where the template
272
+ applies it; a filter a project tag library the template loads replaces is no call),
273
+ and where they name a template the analyzer cannot read, or load a library it cannot
274
+ read while applying a filter of that library's name (`unread`).
266
275
  The shipped `vulnerable-dependency`, `reachable-vulnerability` and `dependency-policy`
267
276
  plugins are of this kind.
268
277
 
@@ -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
@@ -156,9 +162,9 @@ It establishes escaping only when all of the following hold:
156
162
  - No Python file of the project sets `autoescape` to `False`.
157
163
 
158
164
  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
165
+ template file it cannot read, a template named by an expression, a template found
166
+ through a `DIRS` entry not named `templates`, one shipped by an installed package, one
167
+ rendered through `get_template`, and Flask's Jinja2 templates. The flow is then reported as before. A single-file check
162
168
  reads no template.
163
169
 
164
170
  ### Dangerous API usage
@@ -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.17.0"},
250
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.19.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.17.0",
284
+ "name": "coretrace-python-analyzer", "version": "0.19.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
@@ -502,10 +536,14 @@ A template the analyzer cannot read may apply any filter. The project names one
502
536
  `render`, `render_to_string`, `TemplateResponse`, an `{% include %}` or an `{% extends %}`
503
537
  names a template by an expression, or by a name found under no `templates` directory,
504
538
  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.
539
+ too, and so does a template loading a tag library the analyzer cannot read, or find,
540
+ or finds in several apps registering different filters, where it then applies a filter
541
+ of a built-in's name, since the library may replace it. An advisory whose entry points
542
+ include a template filter then stays `under_investigation`, and the notes list where
543
+ the project names each such template (`app.views:12 names a template by an
544
+ expression`, `app/templates/app/profile.html:1 loads 'crispy_forms_tags', not found`).
545
+ A template named elsewhere, such as a class-based view's `template_name`, is not
546
+ checked.
509
547
 
510
548
  `not_affected` needs the lock-file check because the analyzer reads the project's code,
511
549
  not the code of installed packages. A framework calling the vulnerable function on the
@@ -513,7 +551,7 @@ project's behalf would go unseen. Without a lock file, a package is never
513
551
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
514
552
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
515
553
  cannot know it. Its `tooling` names the analyzer and each component with its digest
516
- (`coretrace-python-analyzer 0.17.0; curated-advisories 2026.09.25 (sha256:…)`), so the
554
+ (`coretrace-python-analyzer 0.19.0; curated-advisories 2026.09.25 (sha256:…)`), so the
517
555
  `@id` changes with the advisory data a statement was decided with.
518
556
 
519
557
  ## Large projects
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.17.0"
7
+ version = "0.19.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.17.0"
3
+ __version__ = "0.19.0"
4
4
 
@@ -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 = 11
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:
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  import concurrent.futures
10
10
  import hashlib
11
11
  import multiprocessing
12
+ from collections import Counter
12
13
  from collections.abc import Callable, Iterable, Mapping, Sequence
13
14
  from dataclasses import dataclass, field, replace
14
15
  from pathlib import Path
@@ -88,6 +89,7 @@ from coretrace_python.interprocedural import (
88
89
  TemplateCallsAnalysis,
89
90
  build_module_graph,
90
91
  discover_sources,
92
+ name_modules,
91
93
  project_symbol,
92
94
  )
93
95
  from coretrace_python.ir import PYIR_SCHEMA_VERSION
@@ -371,7 +373,11 @@ def analyze_project(
371
373
  strongly connected components of the module graph, imports first; with ``jobs``
372
374
  above one the components of a wave are analysed in that many processes (§29).
373
375
  ``advisory_files`` add to the ``advisories.json`` at ``root``; ``policy_file``
374
- replaces the ``coretrace-policy.toml`` there."""
376
+ replaces the ``coretrace-policy.toml`` there.
377
+
378
+ Every module is known by the name ``name_modules`` gives its file: its import name,
379
+ or its path from the root when another file has that import name. Such files carry
380
+ an ``ambiguous-module`` note and are covered as ``ambiguous``."""
375
381
 
376
382
  if jobs < 1:
377
383
  raise ValueError("jobs must be at least 1")
@@ -398,18 +404,18 @@ def analyze_project(
398
404
  files: dict[str, SourceFile] = {}
399
405
  unreadable: list[tuple[Path, str]] = []
400
406
  coverage: list[FileCoverage] = []
401
- discovered = discover_sources(root, sources, unreadable)
407
+ named = name_modules(root, discover_sources(root, sources, unreadable))
402
408
  for path, reason in unreadable:
403
409
  findings.append(_note("syntax-error", f"{path}: {reason}", sources.add_source(str(path), ""), 1))
404
410
  coverage.append(FileCoverage(str(path), "unreadable", 0, 0))
405
- for source in discovered:
411
+ for name, source in named.items():
406
412
  try:
407
- modules[source.module_name] = build_hir(source)
413
+ modules[name] = build_hir(source)
408
414
  except (ParseError, HIRBuildError) as error:
409
415
  findings.append(_note("syntax-error", str(error), source, 1))
410
416
  coverage.append(FileCoverage(str(source.source_id), "syntax-error", 0, 0))
411
417
  continue
412
- files[source.module_name] = source
418
+ files[name] = source
413
419
 
414
420
  managers = {name: _register_all(module) for name, module in modules.items()}
415
421
  probe = next(iter(managers.values()), None) or _register_all(build_hir(sources.add_source("<empty>", "")))
@@ -439,6 +445,8 @@ def analyze_project(
439
445
  graph = build_module_graph(
440
446
  {name: files[name] for name in analysable}, {name: modules[name] for name in analysable}, imports
441
447
  )
448
+ shared = _shared_names(graph)
449
+ findings.extend(_ambiguity_notes(graph, shared, root))
442
450
  routes: dict[SymbolId, EntryPoint] = {}
443
451
  for name in sorted(analysable):
444
452
  for symbol, registered in _routes_of(analysable[name]).items():
@@ -447,7 +455,8 @@ def analyze_project(
447
455
  templates = project_templates(root)
448
456
  clearing = models.clearing(escaped)
449
457
  template_calls = models.template_calls(
450
- templates.filters, request_processor(modules[name] for name in sorted(analysable))
458
+ templates.filters,
459
+ request_processor((modules[name], analysable[name].get(ScopeAnalysis)) for name in sorted(analysable)),
451
460
  )
452
461
  for manager in analysable.values():
453
462
  manager.provide(RegisteredRoutes, routes)
@@ -514,15 +523,16 @@ def analyze_project(
514
523
  if pool is not None:
515
524
  pool.shutdown()
516
525
 
517
- index = _seed(results, graph, frozenset())
526
+ index = _index(results, {name: name for name in results})
518
527
  call_graphs: dict[str, CallGraph] = {}
519
528
  functions: dict[str, tuple[ModuleFunction, ...]] = {}
520
529
  for name in sorted(analysable):
521
530
  entry = results[name]
522
531
  findings.extend(entry.findings)
523
532
  unsupported = sum(1 for f in entry.findings if f.rule_id == "unsupported-syntax")
533
+ status = "ambiguous" if files[name].module_name in shared else "analysed"
524
534
  coverage.append(
525
- FileCoverage(str(files[name].source_id), "analysed", len(entry.functions), len(entry.functions) - unsupported)
535
+ FileCoverage(str(files[name].source_id), status, len(entry.functions), len(entry.functions) - unsupported)
526
536
  )
527
537
  functions[name] = entry.functions
528
538
  sites: dict[str, list[CallSite]] = {function.name: [] for function in entry.functions}
@@ -644,24 +654,76 @@ def _advisory_paths(root: Path, advisory_files: Sequence[Path]) -> tuple[Path, .
644
654
  return (*([default] if default.is_file() else []), *advisory_files)
645
655
 
646
656
 
657
+ def _shared_names(graph: ModuleGraph) -> dict[str, list[str]]:
658
+ """The import names several analysable files have, each with those files' modules."""
659
+
660
+ by_name: dict[str, list[str]] = {}
661
+ for name in graph.modules:
662
+ by_name.setdefault(graph.source(name).module_name, []).append(name)
663
+ return {import_name: names for import_name, names in by_name.items() if len(names) > 1}
664
+
665
+
666
+ def _ambiguity_notes(graph: ModuleGraph, shared: Mapping[str, list[str]], root: Path) -> list[Finding]:
667
+ """One note on each file whose import name another file shares, since a symbol of
668
+ that name may denote a function of either, and one on each importer whose import of
669
+ such a name no root resolved, since calls through it are not followed."""
670
+
671
+ def located(name: str) -> str:
672
+ return _located(_path_of(graph.source(name)), root)
673
+
674
+ notes: list[Finding] = []
675
+ for import_name, names in sorted(shared.items()):
676
+ for name in names:
677
+ others = ", ".join(located(other) for other in names if other != name)
678
+ message = (
679
+ f"{located(name)} is the module '{import_name}' like {others}: a symbol "
680
+ f"python.{import_name}.* may name a function of either, and an import of "
681
+ f"'{import_name}' resolves only from its own root"
682
+ )
683
+ notes.append(_note("ambiguous-module", message, graph.source(name), 1))
684
+ for name in graph.modules:
685
+ for import_name in sorted(graph.unresolved(name)):
686
+ candidates = " or ".join(located(other) for other in shared[import_name])
687
+ message = f"'{import_name}' imported here is {candidates}: calls into it are not followed"
688
+ notes.append(_note("ambiguous-module", message, graph.source(name), 1))
689
+ return notes
690
+
691
+
647
692
  def _seed(results: Mapping[str, CachedModule], graph: ModuleGraph, component: frozenset[str]) -> SummaryIndex:
648
693
  """The summaries a component starts from: those of every module it imports,
649
- transitively, all final by the time its wave runs; the whole index for no component."""
650
-
651
- if component:
652
- wanted: set[str] = set()
653
- pending = list(component)
654
- while pending:
655
- for imported in graph.imports(pending.pop()):
656
- if imported in results and imported not in wanted:
657
- wanted.add(imported)
658
- pending.append(imported)
659
- else:
660
- wanted = set(results)
694
+ transitively, all final by the time its wave runs, each under the import name the
695
+ component's code refers to it by. Of several files with one import name, only the
696
+ one a member imports directly, resolved in its own root, goes under that name: the
697
+ others are reached through summaries that inline them already, and a name no
698
+ member's root resolved is followed into neither file."""
699
+
700
+ wanted: set[str] = set()
701
+ pending = list(component)
702
+ while pending:
703
+ for imported in graph.imports(pending.pop()):
704
+ if imported in results and imported not in wanted:
705
+ wanted.add(imported)
706
+ pending.append(imported)
707
+ direct = {imported for member in component for imported in graph.imports(member)}
708
+ by_import: dict[str, list[str]] = {}
709
+ for name in wanted:
710
+ by_import.setdefault(graph.source(name).module_name, []).append(name)
711
+ names: dict[str, str] = {}
712
+ for import_name, modules in by_import.items():
713
+ chosen = modules if len(modules) == 1 else [name for name in modules if name in direct]
714
+ if len(chosen) == 1:
715
+ names[chosen[0]] = import_name
716
+ return _index(results, names)
717
+
718
+
719
+ def _index(results: Mapping[str, CachedModule], names: Mapping[str, str]) -> SummaryIndex:
720
+ """The summaries of the modules in ``names``, each function under the project symbol
721
+ of the module name given for its module."""
722
+
661
723
  return SummaryIndex(
662
724
  {
663
- project_symbol(name, function): summary
664
- for name in sorted(wanted)
725
+ project_symbol(module, function): summary
726
+ for name, module in sorted(names.items())
665
727
  for function, summary in results[name].summaries.items()
666
728
  }
667
729
  )
@@ -674,8 +736,12 @@ def _analyse_managers(
674
736
  affected: Affected,
675
737
  ) -> dict[str, CachedModule]:
676
738
  """Analyse one component: iterate its summaries to a fixpoint over ``seed`` (§21),
677
- then extract what the rest of the run needs from each module."""
739
+ then extract what the rest of the run needs from each module. Each member registers
740
+ its functions under its import name, what the others' code refers to it by; a name
741
+ two members share, a cycle across roots, is registered by neither."""
678
742
 
743
+ counts = Counter(manager.module.name for manager in managers.values())
744
+ names = {name: manager.module.name for name, manager in managers.items() if counts[manager.module.name] == 1}
679
745
  index = seed
680
746
  for manager in managers.values():
681
747
  manager.provide(ProjectSummaries, index)
@@ -684,9 +750,9 @@ def _analyse_managers(
684
750
  {
685
751
  **seed.summaries,
686
752
  **{
687
- project_symbol(name, function): summary
688
- for name, manager in managers.items()
689
- for function, summary in _summaries_of(manager).items()
753
+ project_symbol(module, function): summary
754
+ for name, module in names.items()
755
+ for function, summary in _summaries_of(managers[name]).items()
690
756
  },
691
757
  }
692
758
  )
@@ -701,8 +767,9 @@ def _analyse_managers(
701
767
 
702
768
  @dataclass(frozen=True)
703
769
  class _Batch:
704
- """One component handed to a worker process: it rebuilds the configuration from the
705
- project root and the plugin roots, so only paths and the imported summaries travel."""
770
+ """One component handed to a worker process, its files under the engine's module
771
+ names: it rebuilds the configuration from the project root and the plugin roots, so
772
+ only paths and the imported summaries travel."""
706
773
 
707
774
  root: Path
708
775
  plugin_roots: tuple[Path, ...]
@@ -1,10 +1,12 @@
1
1
  """Analysis coverage: which files and functions a run actually looked at.
2
2
 
3
3
  "No findings" only means something when the reader knows what was analysed. Each file
4
- is ``analysed``, a ``syntax-error`` (the frontend rejected it) or ``unreadable`` (it could
5
- not be decoded); analysed files count their functions and how many lowered. The
6
- templates the project names but the run could not read are listed, each where the
7
- project names it: they may call any template filter.
4
+ is ``analysed``, ``ambiguous`` (analysed, but another file has its Python module name,
5
+ so a symbol of that name may denote a function of either and an import of it resolves
6
+ only from its own root), a ``syntax-error`` (the frontend rejected it) or ``unreadable``
7
+ (it could not be decoded); analysed and ambiguous files count their functions and how
8
+ many lowered. The templates the project names but the run could not read are listed,
9
+ each where the project names it: they may call any template filter.
8
10
  """
9
11
 
10
12
  from __future__ import annotations
@@ -19,6 +19,7 @@ from coretrace_python.interprocedural.modulegraph import (
19
19
  build_module_graph,
20
20
  discover_files,
21
21
  discover_sources,
22
+ name_modules,
22
23
  project_symbol,
23
24
  )
24
25
  from coretrace_python.interprocedural.summaries import (
@@ -73,5 +74,6 @@ __all__ = [
73
74
  "cleared_by",
74
75
  "discover_files",
75
76
  "discover_sources",
77
+ "name_modules",
76
78
  "project_symbol",
77
79
  ]
@@ -132,12 +132,16 @@ class SignaturesAnalysis(Analysis[tuple[Signature, ...]]):
132
132
  @dataclass(frozen=True)
133
133
  class ModuleFunction:
134
134
  """One function of a module as a project plugin sees it: the name the call graph
135
- gives it (``Class.method``, ``outer.inner``, ``<module>``), where it is, and the label
136
- of the entry point it is (``http`` for a route, ``argv`` for a command), if any."""
135
+ gives it (``Class.method``, ``outer.inner``, ``<module>``), where it is, the label
136
+ of the entry point it is (``http`` for a route, ``argv`` for a command), if any, and
137
+ the module-level ``aliases`` bound to it by assigning one name to another
138
+ (``main = actual``), transitively and in source order; a method or a nested
139
+ function has none."""
137
140
 
138
141
  name: str
139
142
  span: SourceSpan
140
143
  entry_point: str | None = None
144
+ aliases: tuple[str, ...] = ()
141
145
 
142
146
 
143
147
  class CallGraph: