coretrace-python-analyzer 0.6.0__tar.gz → 0.8.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 (266) hide show
  1. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/CHANGELOG.md +21 -0
  2. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/docs/plugins.md +22 -4
  4. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/docs/usage.md +44 -7
  5. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/__init__.py +1 -1
  7. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/django/django_models.py +9 -0
  8. coretrace_python_analyzer-0.8.0/src/coretrace_python/bundled/models/http_clients/http_client_models.py +52 -0
  9. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cache.py +18 -3
  10. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cli.py +2 -0
  11. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/engine.py +39 -7
  12. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/refutation.py +111 -20
  13. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/interprocedural/__init__.py +8 -0
  14. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/interprocedural/summaries.py +144 -31
  15. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/api.py +8 -0
  16. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/taint/__init__.py +5 -0
  17. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/taint/engine.py +33 -14
  18. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/taint/models.py +61 -15
  19. coretrace_python_analyzer-0.8.0/src/coretrace_python/taint/templates.py +250 -0
  20. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/Chatbot.json +2 -1
  21. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/DjangoGoat.json +2 -1
  22. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/Flask-Image-Editing-Website.json +2 -1
  23. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/InsecureGram.json +12 -6
  24. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/MiniBookApiServer.json +4 -2
  25. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/Vulnerable_fastapi.json +8 -4
  26. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/dvpwa.json +2 -1
  27. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/full-stack-fastapi-template.json +2 -1
  28. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/healthchecks.json +8 -88
  29. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/httpie.json +4 -8
  30. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/luigi.json +4 -2
  31. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/pygoat.json +26 -37
  32. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +10 -5
  33. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/vulnerable-flask.json +10 -5
  34. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_cache.py +13 -1
  35. coretrace_python_analyzer-0.8.0/tests/test_django_safe_values.py +66 -0
  36. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_interprocedural.py +6 -3
  37. coretrace_python_analyzer-0.8.0/tests/test_project_models.py +117 -0
  38. coretrace_python_analyzer-0.8.0/tests/test_project_validators.py +93 -0
  39. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_refutation.py +25 -0
  40. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_regression.py +6 -3
  41. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_release.py +1 -1
  42. coretrace_python_analyzer-0.8.0/tests/test_ssrf_targets.py +124 -0
  43. coretrace_python_analyzer-0.8.0/tests/test_summary_sanitizers.py +136 -0
  44. coretrace_python_analyzer-0.8.0/tests/test_template_escaping.py +178 -0
  45. coretrace_python_analyzer-0.6.0/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -43
  46. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/.github/workflows/ci.yml +0 -0
  47. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/.github/workflows/release.yml +0 -0
  48. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/.gitignore +0 -0
  49. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/LICENSE +0 -0
  50. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/NOTICE +0 -0
  51. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/README.md +0 -0
  52. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/docs/architecture.md +0 -0
  53. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/examples/add.py +0 -0
  54. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/examples/canonical_symbols.py +0 -0
  55. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/__main__.py +0 -0
  56. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/abstract/__init__.py +0 -0
  57. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/abstract/constants.py +0 -0
  58. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/abstract/heap.py +0 -0
  59. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/abstract/ranges.py +0 -0
  60. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/abstract/values.py +0 -0
  61. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/analysis/__init__.py +0 -0
  62. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/analysis/manager.py +0 -0
  63. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/analysis/provider.py +0 -0
  64. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  65. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  66. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  67. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  68. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  70. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  71. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  72. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  73. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  74. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  75. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  76. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  77. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  78. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  79. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  80. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  81. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  83. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  84. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  85. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  86. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  87. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  88. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  90. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  92. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  94. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  95. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  97. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  99. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  100. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  101. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  103. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  104. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  105. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  106. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  107. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  108. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  109. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  110. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  111. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  112. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  113. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  114. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  115. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  116. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  117. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  118. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  119. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  120. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  121. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  122. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  123. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  124. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  125. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  126. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  127. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  128. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  129. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  130. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  131. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  132. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cfg/__init__.py +0 -0
  133. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cfg/builder.py +0 -0
  134. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cfg/dominance.py +0 -0
  135. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/cfg/model.py +0 -0
  136. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  137. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  138. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dataflow/solver.py +0 -0
  139. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/__init__.py +0 -0
  140. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/advisories.py +0 -0
  141. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/correlation.py +0 -0
  142. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/graph.py +0 -0
  143. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/policy.py +0 -0
  144. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/sbom.py +0 -0
  145. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/dependency/vex.py +0 -0
  146. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/__init__.py +0 -0
  147. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/baseline.py +0 -0
  148. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/coverage.py +0 -0
  149. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/model.py +0 -0
  150. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/findings/suppressions.py +0 -0
  151. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/frontend/__init__.py +0 -0
  152. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  153. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/frontend/parser.py +0 -0
  154. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/hir/__init__.py +0 -0
  155. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/hir/nodes.py +0 -0
  156. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/hir/visitors.py +0 -0
  157. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
  158. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  159. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/__init__.py +0 -0
  160. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/defuse.py +0 -0
  161. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/lowering.py +0 -0
  162. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/model.py +0 -0
  163. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/printer.py +0 -0
  164. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/ir/ssa.py +0 -0
  165. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/__init__.py +0 -0
  166. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/detectors.py +0 -0
  167. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/loader.py +0 -0
  168. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/manifest.py +0 -0
  169. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/registry.py +0 -0
  170. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/plugins/secrets.py +0 -0
  171. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/reporters/__init__.py +0 -0
  172. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/reporters/json_format.py +0 -0
  173. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/reporters/report.py +0 -0
  174. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/reporters/sarif.py +0 -0
  175. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/reporters/text.py +0 -0
  176. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/semantic/__init__.py +0 -0
  177. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/semantic/identity.py +0 -0
  178. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/semantic/imports.py +0 -0
  179. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/semantic/scopes.py +0 -0
  180. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/semantic/symbols.py +0 -0
  181. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/source/__init__.py +0 -0
  182. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/source/manager.py +0 -0
  183. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/source/model.py +0 -0
  184. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/src/coretrace_python/taint/routes.py +0 -0
  185. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/NPL-IA.json +0 -0
  186. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  187. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/my_gdb.json +0 -0
  188. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/network-topologer.json +0 -0
  189. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/expected/yt-fts.json +0 -0
  190. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  191. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  192. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  193. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  194. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/regression/repositories.toml +0 -0
  195. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_advisory_evidence.py +0 -0
  196. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_advisory_feeds.py +0 -0
  197. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_aiohttp_models.py +0 -0
  198. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_analysis_manager.py +0 -0
  199. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_architecture.py +0 -0
  200. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_argument_conditions.py +0 -0
  201. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_audit_detectors.py +0 -0
  202. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_baseline.py +0 -0
  203. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_cfg.py +0 -0
  204. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_class_keywords_with_targets.py +0 -0
  205. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_cli.py +0 -0
  206. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_closures.py +0 -0
  207. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_config_secrets_coverage.py +0 -0
  208. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_constants.py +0 -0
  209. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_correlation.py +0 -0
  210. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_credential_context.py +0 -0
  211. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_dataflow.py +0 -0
  212. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_dependencies.py +0 -0
  213. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_detectors.py +0 -0
  214. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_discovery.py +0 -0
  215. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_django_http_clients.py +0 -0
  216. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_django_routes.py +0 -0
  217. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_docs.py +0 -0
  218. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_dominance.py +0 -0
  219. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_emit_ir.py +0 -0
  220. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_exceptions.py +0 -0
  221. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_factories_match_loop_else.py +0 -0
  222. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_fail_on.py +0 -0
  223. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_fastapi_cli_precision.py +0 -0
  224. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_frameworks.py +0 -0
  225. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_frontend_gaps.py +0 -0
  226. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_heap.py +0 -0
  227. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_hex_digests.py +0 -0
  228. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_hir_builder.py +0 -0
  229. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_imports.py +0 -0
  230. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_interprocedural_taint.py +0 -0
  231. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_ir.py +0 -0
  232. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_match_patterns.py +0 -0
  233. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_module_level.py +0 -0
  234. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_nonlocal_writeback.py +0 -0
  235. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_operator_sources.py +0 -0
  236. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_packaging.py +0 -0
  237. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_parallel.py +0 -0
  238. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_parser.py +0 -0
  239. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_plaintext_credentials.py +0 -0
  240. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_plugins.py +0 -0
  241. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_project.py +0 -0
  242. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_pyir_imports.py +0 -0
  243. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_read_entry_points.py +0 -0
  244. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_real_code_precision.py +0 -0
  245. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_redirect_targets.py +0 -0
  246. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_refutation_evidence.py +0 -0
  247. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_relative_paths.py +0 -0
  248. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_remaining_limits.py +0 -0
  249. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_reporters.py +0 -0
  250. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_safe_arguments.py +0 -0
  251. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_scale.py +0 -0
  252. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_scopes.py +0 -0
  253. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_secrets_sbom.py +0 -0
  254. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_self_state.py +0 -0
  255. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_source_manager.py +0 -0
  256. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_ssa.py +0 -0
  257. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_suppressions.py +0 -0
  258. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_symbol_resolution.py +0 -0
  259. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_symbols.py +0 -0
  260. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_syntactic_rules.py +0 -0
  261. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_syntax_control_flow.py +0 -0
  262. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_syntax_coverage.py +0 -0
  263. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_syntax_expressions.py +0 -0
  264. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_taint.py +0 -0
  265. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_tornado_bottle_models.py +0 -0
  266. {coretrace_python_analyzer-0.6.0 → coretrace_python_analyzer-0.8.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 (2026-09-25)
4
+
5
+ ### Precision
6
+
7
+ - A `Validator` proves its argument safe only for the kinds it declares. A validator declared for `REDIRECT` used to refute a command injection through the same value; the flow is now a hotspot, and it is refuted only when the validator covers every kind reaching the sink (#143).
8
+ - A sanitizer called inside a project function protects the flows through that function, for every caller. That covers a helper that escapes before answering, a view redirecting to `reverse(...)`, and a helper rendering a template the analyzer shows escaping. Function summaries keep, for each parameter, the taint kinds cleared on every path from it to an external call or to the return value. A path that skips the sanitizer, or a kind it does not clear, still reaches the sink. The cache format is bumped (#134).
9
+
10
+ ### Plugins
11
+
12
+ - `Plugin.project_models(root)` lets a plugin read models from the project under analysis, such as validators the project declares in a file. It is called once per directory check, in every process, and its models are part of the cache key. A model declared wrongly raises `ModelError`, which the command line now reports as an error (exit status 2) instead of a traceback (#140).
13
+ - A `Validator` model may name a function of the project by its project symbol (`python.hc.accounts.views._allow_redirect`), and the refutation recognises it in its own module too, where a call to it has no imported symbol. A project can thus declare its own validation helpers, and a flow they guard is refuted (#134).
14
+
15
+ ## 0.7.0 (2026-09-25)
16
+
17
+ ### Precision
18
+
19
+ - A dominating guard makes a flow a hotspot only when it examines what the sink receives. `if request.method == 'POST':` before `request.POST['cmd']` no longer lowers a vulnerability to a hotspot. A test of the value itself, of the same attribute, of a method called on it (`form.is_valid()`), or of any part of an object the flow passes whole still does. The regression snapshots record the verdict of each taint finding (#135).
20
+ - The SSRF sinks of Requests and httpx read the destination only: the URL (the first argument, the second after the method for `request` and `stream`, or `url=`) or the prepared request `send` takes. Attacker data in the body, the JSON, the headers or the query parameters is no longer a server-side request forgery. `Sink` gains `keywords` next to `positions`, and function summaries keep the name of each keyword argument, so a project function forwarding `url=` still reaches the sink; the cache format is bumped (#128).
21
+ - `render_to_string` escapes what it renders where the analyzer can establish it: on a directory check, it reads the project's Django templates, and a template escapes when neither it nor what it extends or includes marks output safe, uses a tag, filter or library beyond Django's own, or prints a variable where HTML escaping does not protect it, and no settings turn autoescaping off. Data reaching a response through such a template is no longer `xss`; any other template keeps the flow reported. `TemplateRender` declares such rendering calls, and the escaped templates are part of the cache key (#130).
22
+ - Django's `csrf.get_token` returns a token of letters and digits whatever the request holds, and `reverse`/`reverse_lazy` build a local path that attacker data in their arguments or query cannot turn into another host: the first is no longer an injection, the second no longer an open redirect. `reverse` still carries other kinds, since it leaves `'` unquoted (#132).
23
+
3
24
  ## 0.6.0 (2026-09-24)
4
25
 
5
26
  ### Reports
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.6.0
3
+ Version: 0.8.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
@@ -141,22 +141,40 @@ class BottleModels(ModelPlugin):
141
141
  | Model | Declares |
142
142
  |---|---|
143
143
  | `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. |
144
- | `Sink(symbol, kinds, positions=())` | A call dangerous for `kinds`; `positions` limits a kind to given argument indexes, as SQL sinks read the statement only. |
145
- | `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`. |
144
+ | `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. |
145
+ | `Sanitizer(symbol, kinds)` | A call whose result is safe for `kinds`, including when the call is inside a project function the data goes through. |
146
146
  | `EntryPoint(symbol, label, kinds=ALL)` | A decorator or a class base whose functions receive their parameters as `label` input, such as `flask.Flask.route`. |
147
147
  | `TypedParameter(symbol, label, kinds=ALL)` | A class whose annotated parameters carry `label` input, such as Django's `HttpRequest`. |
148
148
  | `NamedParameter(pattern, label, kinds)` | A regular expression on parameter names; matching parameters carry `kinds`, as `password` carries `CREDENTIAL`. |
149
149
  | `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. |
150
150
  | `SuffixSink(suffix, kinds, positions=())` | A sink matched by the end of the symbol, for methods of any class such as `objects.raw`. |
151
151
  | `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. |
152
- | `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe, such as `re.fullmatch`; a flow guarded by it is refuted. |
152
+ | `TemplateRender(symbol, position=0, keyword="template_name")` | A call rendering, with autoescaping, the template named by its argument at `position` or `keyword`, such as `render_to_string`. What it returns carries no `HTML` when the name is written in the call and the project's template escapes everything it prints (see the usage guide). |
153
+ | `Validator(symbol, kinds=ALL, argument=0)` | A callable whose truth proves its argument safe for `kinds`, such as `re.fullmatch`. A flow guarded by it is refuted when it carries no other kind into the sink, and is a hotspot otherwise: a redirect validator does not make a shell command safe. `symbol` may be a function of the project, by its project symbol (`python.hc.accounts.views._allow_redirect`), which then counts wherever it is called, in its own module too. |
153
154
  | `AuthorizationGuard(symbol, label)` | A decorator or a condition restricting who reaches the code, such as `login_required`; a flow behind it is a hotspot. |
154
155
 
155
156
  Two plugins may describe the same symbol. An identical model is registered once; two
156
- `Sink` models merge their kinds and positions, so a plugin can add `SQL` to a sink
157
+ `Sink` models merge their kinds, positions and keywords, so a plugin can add `SQL` to a sink
157
158
  another plugin declared for `COMMAND`; any other difference is a conflict that stops
158
159
  the analysis with a `ModelError` naming both plugins.
159
160
 
161
+ A plugin may also read models from the project it analyses: `project_models(self, root)`
162
+ returns them, from a file at `root` for instance. It is called once per directory check,
163
+ in every process analysing it, before any module. Its models join the plugin's own and
164
+ are part of what cached results depend on. A single-file check reads no project. A
165
+ model declared wrongly raises `ModelError`, which stops the check with its message
166
+ (exit status 2). This is how a project declares validators of its own:
167
+
168
+ ```python
169
+ class DeclaredValidators(ModelPlugin):
170
+ name: ClassVar[str] = "declared-validators"
171
+
172
+ def project_models(self, root: Path) -> Sequence[Model]:
173
+ path = root / "validators.txt"
174
+ lines = path.read_text(encoding="utf-8").splitlines() if path.is_file() else []
175
+ return tuple(Validator(SymbolId(line)) for line in lines if line)
176
+ ```
177
+
160
178
  A model plugin may also carry `advisories`, a tuple of `Advisory` values
161
179
  (`coretrace_python.dependency`) with the package, the vulnerable range and the affected
162
180
  symbols, as the shipped `sample-advisories` plugin does. Requirements matching them are
@@ -101,15 +101,19 @@ confidence (`high`, `medium`, `low`).
101
101
  ### Taint flows
102
102
 
103
103
  Reported when attacker-controlled data reaches a sensitive call without passing through
104
- a sanitizer for that kind of sink.
104
+ a sanitizer for that kind of sink. The sanitizer may also sit inside a project function
105
+ the data goes through, like a helper that escapes before it answers, or a view that
106
+ redirects to `reverse(...)`. It then protects every caller, on every path through the
107
+ function where it is called. What a function stores into an object or a `nonlocal`
108
+ variable is not treated that way yet.
105
109
 
106
110
  | Rule | Reached sink |
107
111
  |---|---|
108
112
  | `command-injection` | Shell or process execution (`os.system`, `subprocess` with a string, …). |
109
113
  | `sql-injection` | A database statement (`cursor.execute`, SQLAlchemy `text`, Django `raw`, …). Parameters of a parameterised query are not statements. |
110
114
  | `path-traversal` | A file system path (`open`, `send_file`, `os.remove`, …). |
111
- | `ssrf` | The URL of an HTTP client request (Requests, httpx, `urllib`). |
112
- | `xss` | An HTTP response body built without escaping. |
115
+ | `ssrf` | The URL of an HTTP client request (Requests, httpx, `urllib`), by position or as `url=`; the body, headers and query parameters are not the destination. |
116
+ | `xss` | An HTTP response body built without escaping. A Django template rendered by `render_to_string` escapes, where the analyzer can establish it (below). |
113
117
  | `insecure-deserialization` | `pickle`, `marshal`, `dill`, `jsonpickle`, `shelve`, and the YAML loaders (`yaml.load`, `load_all`, `full_load`, `unsafe_load` and their several-document versions); `yaml.load` and `load_all` given a safe loader (`SafeLoader`, `BaseLoader`) are not. |
114
118
  | `open-redirect` | An HTTP redirect target. |
115
119
  | `plaintext-credential-storage` | A parameter named like a password stored in a database without hashing. Medium confidence, since a name is a hint. |
@@ -118,9 +122,42 @@ a sanitizer for that kind of sink.
118
122
  Each flow is judged before it is reported. A dominating guard that proves the value
119
123
  safe (`isdigit()`, membership in a constant collection, equality with a constant, a
120
124
  numeric value proven by `int()`, `len()` or bounded arithmetic, a validator such as
121
- `re.fullmatch`) refutes it and nothing is reported. A guard that only mentions the value,
122
- or an authorization decorator such as `login_required`, makes it a hotspot reported at
123
- medium confidence. The verdict and its evidence are in the finding's metadata.
125
+ `re.fullmatch`) refutes it and nothing is reported. A hotspot is reported at medium
126
+ confidence. Two things make a flow a hotspot:
127
+
128
+ - A guard that examines the data without proving it safe. That covers a test of the value
129
+ itself (`if cmd:`, `len(cmd) < 10`), of the same attribute, or of a method called on
130
+ it (`form.is_valid()`).
131
+ - An authorization decorator such as `login_required`.
132
+
133
+ A guard that reads another attribute of the object the data comes from is no guard of
134
+ that data. `if request.method == 'POST':` before `os.system(request.POST['cmd'])` leaves
135
+ a vulnerability. When the flow passes the object whole, to the sink or to a function,
136
+ a guard on any of its attributes still counts. The verdict and its evidence are in the
137
+ finding's metadata.
138
+
139
+ Django's `render_to_string(name, context)` returns HTML in which autoescaping escaped
140
+ every variable, so data reaching a response through it is no `xss`, if the template really
141
+ escapes. On a directory check, the analyzer reads the project's templates to decide it.
142
+
143
+ It establishes escaping only when all of the following hold:
144
+
145
+ - It finds every template of that name under a `templates` directory, by the path Django
146
+ uses (`app/page.html`), and everything those templates extend or include.
147
+ - None of them marks output safe: `|safe`, `|safeseq` or `{% autoescape off %}`.
148
+ - None of them uses a tag or filter beyond Django's own, or loads a library beyond its
149
+ built-ins (`static`, `i18n`, `l10n`, `tz`, `cache`, `humanize`).
150
+ - None of them places a variable where HTML escaping does not protect it: a `<script>`
151
+ or `<style>` block, an event handler (`onclick="…"`), a `style` attribute, a URL
152
+ attribute not fixed by a relative or `http(s)://host/` prefix, an unquoted attribute,
153
+ or an attribute name.
154
+ - No Python file of the project sets `autoescape` to `False`.
155
+
156
+ The analyzer never assumes escaping where it cannot read the template. That covers a
157
+ template named by an expression, a template found through a `DIRS` entry not named
158
+ `templates`, one shipped by an installed package, one rendered through `get_template`,
159
+ and Flask's Jinja2 templates. The flow is then reported as before. A single-file check
160
+ reads no template.
124
161
 
125
162
  ### Dangerous API usage
126
163
 
@@ -202,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
202
239
  ```json
203
240
  {
204
241
  "schema_version": 1,
205
- "tool": {"name": "coretrace-python-analyzer", "version": "0.6.0"},
242
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.8.0"},
206
243
  "root": "/home/me/project",
207
244
  "findings": [
208
245
  {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.6.0"
7
+ version = "0.8.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.6.0"
3
+ __version__ = "0.8.0"
4
4
 
@@ -23,6 +23,7 @@ from coretrace_python.taint import (
23
23
  Source,
24
24
  SuffixSink,
25
25
  TaintKind,
26
+ TemplateRender,
26
27
  TypedParameter,
27
28
  )
28
29
 
@@ -119,7 +120,15 @@ class DjangoModels(ModelPlugin):
119
120
  Sink(_sym("django.http.HttpResponseRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
120
121
  Sink(_sym("django.http.HttpResponsePermanentRedirect"), TaintKind.REDIRECT, _TARGET_ONLY),
121
122
  Sanitizer(_sym("django.utils.html.escape"), TaintKind.HTML),
123
+ TemplateRender(_sym("django.template.loader.render_to_string")),
122
124
  Sanitizer(_sym("django.utils.html.conditional_escape"), TaintKind.HTML),
125
+ # The masked CSRF secret: ASCII letters and digits only, a malformed cookie is
126
+ # replaced before it is used.
127
+ Sanitizer(_sym("django.middleware.csrf.get_token"), TaintKind.ALL),
128
+ # A path of the project's URL configuration, never scheme-relative: attacker data
129
+ # in its arguments or query reaches no other host through a redirect.
130
+ *(Sanitizer(_sym(f"django.urls.{name}"), TaintKind.REDIRECT) for name in ("reverse", "reverse_lazy")),
131
+ *(Sanitizer(_sym(f"django.urls.base.{name}"), TaintKind.REDIRECT) for name in ("reverse", "reverse_lazy")),
123
132
  *(AuthorizationGuard(_sym(decorator), label) for decorator, label in _AUTHORIZATION_DECORATORS),
124
133
  # ``urlpatterns = [path('login/', views.log_in)]``: the referenced view is an
125
134
  # entry point wherever it is defined; routers register viewsets.
@@ -0,0 +1,52 @@
1
+ """Requests and httpx models: every request function is a SSRF sink for the argument that
2
+ chooses the destination, and what it returns is an ``http-response`` source, so data
3
+ fetched from a remote server is untrusted. The destination is the URL — first, or second
4
+ after the method, or ``url=`` — or the prepared request a ``send`` takes; the body, the
5
+ headers and the query parameters do not choose the host."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import ClassVar
10
+
11
+ from coretrace_python.plugins import ModelPlugin
12
+ from coretrace_python.semantic.symbols import SymbolId
13
+ from coretrace_python.taint import Model, Sink, Source, TaintKind
14
+
15
+ _METHODS = ("get", "post", "put", "patch", "delete", "head", "options", "request")
16
+
17
+ _CALLERS = (
18
+ "requests",
19
+ "requests.Session",
20
+ "requests.api",
21
+ "httpx",
22
+ "httpx.Client",
23
+ "httpx.AsyncClient",
24
+ )
25
+
26
+ # Where each function takes its destination, by position (the receiver of a method
27
+ # excluded) and by keyword, as its signature declares it.
28
+ _DESTINATIONS = {
29
+ **{f"{caller}.{method}": (0, "url") for caller in _CALLERS for method in _METHODS if method != "request"},
30
+ **{f"{caller}.request": (1, "url") for caller in _CALLERS},
31
+ "httpx.stream": (1, "url"),
32
+ "httpx.Client.stream": (1, "url"),
33
+ "httpx.AsyncClient.stream": (1, "url"),
34
+ "requests.Session.send": (0, "request"),
35
+ "httpx.Client.send": (0, "request"),
36
+ "httpx.AsyncClient.send": (0, "request"),
37
+ }
38
+
39
+
40
+ def _sym(path: str) -> SymbolId:
41
+ return SymbolId(f"python.{path}")
42
+
43
+
44
+ class HttpClientModels(ModelPlugin):
45
+ name: ClassVar[str] = "http-client-models"
46
+ models: ClassVar[tuple[Model, ...]] = (
47
+ *(
48
+ Sink(_sym(function), TaintKind.SSRF, ((TaintKind.SSRF, (position,)),), ((TaintKind.SSRF, (keyword,)),))
49
+ for function, (position, keyword) in _DESTINATIONS.items()
50
+ ),
51
+ *(Source(_sym(function), "http-response") for function in _DESTINATIONS),
52
+ )
@@ -25,6 +25,7 @@ from coretrace_python.findings import Confidence, Finding, Severity
25
25
  from coretrace_python.interprocedural import (
26
26
  Arguments,
27
27
  CallSite,
28
+ Cleared,
28
29
  ExternalCall,
29
30
  ExternalSymbol,
30
31
  FunctionSummary,
@@ -41,7 +42,7 @@ from coretrace_python.interprocedural import (
41
42
  from coretrace_python.semantic.symbols import SymbolId
42
43
  from coretrace_python.source import SourceId, SourceSpan
43
44
 
44
- CACHE_FORMAT = 8
45
+ CACHE_FORMAT = 10
45
46
 
46
47
 
47
48
  @dataclass(frozen=True)
@@ -233,10 +234,12 @@ def _encode_call(call: ExternalCall) -> dict[str, Any]:
233
234
  return {
234
235
  "symbol": str(call.symbol),
235
236
  "arguments": [sorted(deps) for deps in call.argument_dependencies],
236
- "keywords": sorted(call.keyword_dependencies),
237
+ "keywords": [[name, sorted(deps)] for name, deps in call.keyword_dependencies],
237
238
  "location": _encode_span(call.location),
238
239
  "call_site": None if call.call_site is None else _encode_span(call.call_site),
239
240
  "given": _encode_arguments(call.arguments),
241
+ "argument_cleared": [_encode_cleared(c) for c in call.argument_cleared],
242
+ "keyword_cleared": [_encode_cleared(c) for c in call.keyword_cleared],
240
243
  }
241
244
 
242
245
 
@@ -245,13 +248,23 @@ def _decode_call(data: Mapping[str, Any]) -> ExternalCall:
245
248
  return ExternalCall(
246
249
  SymbolId(_string(data["symbol"])),
247
250
  tuple(_indices(deps) for deps in data["arguments"]),
248
- _indices(data["keywords"]),
251
+ tuple((None if name is None else _string(name), _indices(deps)) for name, deps in data["keywords"]),
249
252
  _decode_span(data["location"]),
250
253
  None if site is None else _decode_span(site),
251
254
  _decode_arguments(data["given"]),
255
+ tuple(_decode_cleared(c) for c in data["argument_cleared"]),
256
+ tuple(_decode_cleared(c) for c in data["keyword_cleared"]),
252
257
  )
253
258
 
254
259
 
260
+ def _encode_cleared(cleared: Cleared) -> list[list[int]]:
261
+ return [[parameter, bits] for parameter, bits in cleared]
262
+
263
+
264
+ def _decode_cleared(data: Any) -> Cleared:
265
+ return tuple((_integer(parameter), _integer(bits)) for parameter, bits in data)
266
+
267
+
255
268
  def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
256
269
  return {
257
270
  "name": summary.name,
@@ -275,6 +288,7 @@ def _encode_summary(summary: FunctionSummary) -> dict[str, Any]:
275
288
  for w in summary.nonlocal_writes
276
289
  ],
277
290
  "static": summary.static,
291
+ "return_cleared": _encode_cleared(summary.return_cleared),
278
292
  }
279
293
 
280
294
 
@@ -303,6 +317,7 @@ def _decode_summary(data: Mapping[str, Any]) -> FunctionSummary:
303
317
  for w in data["nonlocal_writes"]
304
318
  ),
305
319
  bool(data["static"]),
320
+ _decode_cleared(data["return_cleared"]),
306
321
  )
307
322
 
308
323
 
@@ -27,6 +27,7 @@ from coretrace_python.reporters import FORMATS, render
27
27
  from coretrace_python.semantic.imports import ImportResolutionError
28
28
  from coretrace_python.semantic.scopes import ScopeError
29
29
  from coretrace_python.source import SourceManager
30
+ from coretrace_python.taint import ModelError
30
31
 
31
32
  EXIT_CLEAN = 0
32
33
  EXIT_FINDINGS = 1
@@ -45,6 +46,7 @@ _ANALYSIS_ERRORS = (
45
46
  AnalysisError,
46
47
  ManifestError,
47
48
  IncompatiblePluginError,
49
+ ModelError,
48
50
  )
49
51
 
50
52
 
@@ -70,6 +70,7 @@ from coretrace_python.interprocedural import (
70
70
  CallGraph,
71
71
  CallGraphAnalysis,
72
72
  CallSite,
73
+ ClearingAnalysis,
73
74
  FunctionSummary,
74
75
  ModuleFunction,
75
76
  ModuleGraph,
@@ -109,6 +110,7 @@ from coretrace_python.source import SourceFile, SourceId, SourceManager, SourceS
109
110
  from coretrace_python.taint import (
110
111
  EntryPoint,
111
112
  EntryPointAnalysis,
113
+ EscapedTemplates,
112
114
  ModelTable,
113
115
  RegisteredRoutes,
114
116
  Routes,
@@ -116,6 +118,7 @@ from coretrace_python.taint import (
116
118
  SecurityModelRegistry,
117
119
  TaintAnalysis,
118
120
  TaintKind,
121
+ escaped_templates,
119
122
  registered_routes,
120
123
  )
121
124
 
@@ -146,6 +149,8 @@ ALL_ANALYSES: tuple[AnyAnalysis, ...] = (
146
149
  RefutationAnalysis,
147
150
  DependencyAnalysis,
148
151
  RegisteredRoutes,
152
+ EscapedTemplates,
153
+ ClearingAnalysis,
149
154
  )
150
155
 
151
156
 
@@ -178,7 +183,15 @@ class ResultsEvicted(TransformationPass):
178
183
 
179
184
  name: ClassVar[str] = "project.results-evicted"
180
185
  preserves: ClassVar[frozenset[AnyAnalysis]] = frozenset(
181
- {*SEMANTIC_ANALYSES, SecurityModelAnalysis, ProjectSummaries, DependencyAnalysis, RegisteredRoutes}
186
+ {
187
+ *SEMANTIC_ANALYSES,
188
+ SecurityModelAnalysis,
189
+ ProjectSummaries,
190
+ DependencyAnalysis,
191
+ RegisteredRoutes,
192
+ EscapedTemplates,
193
+ ClearingAnalysis,
194
+ }
182
195
  )
183
196
 
184
197
  @classmethod
@@ -236,7 +249,9 @@ def build_manager(
236
249
  """A manager with every engine analysis registered and the engine inputs provided."""
237
250
 
238
251
  manager = _register_all(module)
239
- manager.provide(SecurityModelAnalysis, (models or SecurityModelRegistry()).freeze())
252
+ table = (models or SecurityModelRegistry()).freeze()
253
+ manager.provide(SecurityModelAnalysis, table)
254
+ manager.provide(ClearingAnalysis, table.clearing())
240
255
  manager.provide(ProjectSummaries, SummaryIndex())
241
256
  manager.provide(RegisteredRoutes, _routes_of(manager))
242
257
  return manager
@@ -266,10 +281,14 @@ def load_plugins(plugin_roots: Sequence[Path], manager: AnalysisManager) -> Plug
266
281
  return registry
267
282
 
268
283
 
269
- def plugin_models(plugins: Iterable[Plugin]) -> ModelTable:
284
+ def plugin_models(plugins: Iterable[Plugin], root: Path | None = None) -> ModelTable:
285
+ """The models of ``plugins``, with those they read from the project at ``root``."""
286
+
270
287
  models = SecurityModelRegistry()
271
288
  for plugin in plugins:
272
289
  models.register(*plugin.models, origin=plugin.name)
290
+ if root is not None:
291
+ models.register(*plugin.project_models(root), origin=plugin.name)
273
292
  return models.freeze()
274
293
 
275
294
 
@@ -284,7 +303,9 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
284
303
 
285
304
  manager = _register_all(build_hir(source))
286
305
  registry = load_plugins(plugin_roots, manager)
287
- manager.provide(SecurityModelAnalysis, plugin_models(loaded.plugin for loaded in registry))
306
+ table = plugin_models(loaded.plugin for loaded in registry)
307
+ manager.provide(SecurityModelAnalysis, table)
308
+ manager.provide(ClearingAnalysis, table.clearing())
288
309
  manager.provide(ProjectSummaries, SummaryIndex())
289
310
  manager.provide(RegisteredRoutes, _routes_of(manager))
290
311
  findings, supported = _check_module(manager, tuple(loaded.plugin for loaded in registry))
@@ -375,7 +396,7 @@ def analyze_project(
375
396
  (a for plugin in all_plugins for a in plugin.advisories), file_advisories
376
397
  )
377
398
  affected = affected_symbols(dependencies, advisories)
378
- models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
399
+ models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
379
400
  for manager in managers.values():
380
401
  manager.provide(SecurityModelAnalysis, models)
381
402
  manager.provide(DependencyAnalysis, dependencies)
@@ -397,10 +418,14 @@ def analyze_project(
397
418
  for name in sorted(analysable):
398
419
  for symbol, registered in _routes_of(analysable[name]).items():
399
420
  routes.setdefault(symbol, registered)
421
+ escaped = escaped_templates(root)
422
+ clearing = models.clearing(escaped)
400
423
  for manager in analysable.values():
401
424
  manager.provide(RegisteredRoutes, routes)
425
+ manager.provide(EscapedTemplates, escaped)
426
+ manager.provide(ClearingAnalysis, clearing)
402
427
 
403
- configuration = _configuration_key(registry, plugins, models, advisories, dependencies, routes)
428
+ configuration = _configuration_key(registry, plugins, models, advisories, dependencies, routes, escaped)
404
429
  keys = module_keys(
405
430
  graph,
406
431
  {name: fingerprint(configuration, str(files[name].source_id), name, files[name].text) for name in analysable},
@@ -441,6 +466,7 @@ def analyze_project(
441
466
  encode_index(_seed(results, graph, component)),
442
467
  advisory_paths,
443
468
  _encode_routes(routes),
469
+ tuple(sorted(escaped)),
444
470
  ),
445
471
  )
446
472
  for component in pending
@@ -588,6 +614,7 @@ class _Batch:
588
614
  seed: Mapping[str, Any]
589
615
  advisory_paths: tuple[Path, ...] = ()
590
616
  routes: tuple[tuple[str, str, str, int], ...] = ()
617
+ escaped: tuple[str, ...] = ()
591
618
 
592
619
 
593
620
  def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
@@ -606,12 +633,15 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
606
633
  (a for plugin in all_plugins for a in plugin.advisories), file_advisories
607
634
  )
608
635
  affected = affected_symbols(dependencies, advisories)
609
- models = plugin_models(all_plugins).extended(*advisory_sinks(affected))
636
+ models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
610
637
  routes = _decode_routes(batch.routes)
638
+ escaped = frozenset(batch.escaped)
611
639
  for manager in managers.values():
612
640
  manager.provide(SecurityModelAnalysis, models)
613
641
  manager.provide(DependencyAnalysis, dependencies)
614
642
  manager.provide(RegisteredRoutes, routes)
643
+ manager.provide(EscapedTemplates, escaped)
644
+ manager.provide(ClearingAnalysis, models.clearing(escaped))
615
645
  module_plugins = tuple(p for p in all_plugins if not isinstance(p, ProjectPlugin))
616
646
  results = _analyse_managers(managers, decode_index(batch.seed), module_plugins, affected)
617
647
  return {name: encode(entry) for name, entry in results.items()}
@@ -639,6 +669,7 @@ def _configuration_key(
639
669
  advisories: tuple[Advisory, ...],
640
670
  dependencies: DependencyGraph,
641
671
  routes: Routes | None = None,
672
+ escaped: frozenset[str] = frozenset(),
642
673
  ) -> str:
643
674
  """Everything a module's results depend on besides the project sources (§11)."""
644
675
 
@@ -658,6 +689,7 @@ def _configuration_key(
658
689
  repr(dependencies.requirements),
659
690
  repr(dependencies.errors),
660
691
  repr(_encode_routes(routes or {})),
692
+ repr(sorted(escaped)),
661
693
  )
662
694
 
663
695