coretrace-python-analyzer 0.3.0__tar.gz → 0.4.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 (256) hide show
  1. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/CHANGELOG.md +18 -0
  2. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/docs/plugins.md +32 -0
  4. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/docs/usage.md +40 -4
  5. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/pyproject.toml +1 -1
  6. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/__init__.py +1 -1
  7. coretrace_python_analyzer-0.4.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +46 -0
  8. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +2 -0
  9. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +3 -0
  10. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cache.py +1 -1
  11. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/__init__.py +4 -0
  12. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/advisories.py +42 -5
  13. coretrace_python_analyzer-0.4.0/src/coretrace_python/dependency/correlation.py +113 -0
  14. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/graph.py +74 -5
  15. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/engine.py +17 -7
  16. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/callgraph.py +9 -2
  17. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/modulegraph.py +4 -1
  18. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/lowering.py +57 -3
  19. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/__init__.py +4 -0
  20. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/api.py +47 -1
  21. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/loader.py +4 -4
  22. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/scopes.py +3 -1
  23. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/engine.py +9 -2
  24. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Chatbot.json +2 -2
  25. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/DjangoGoat.json +2 -2
  26. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Flask-Image-Editing-Website.json +2 -2
  27. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/InsecureGram.json +2 -2
  28. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/MiniBookApiServer.json +2 -2
  29. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/NPL-IA.json +2 -2
  30. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/Spam-Detection-Project.json +2 -2
  31. coretrace_python_analyzer-0.4.0/tests/regression/expected/Vulnerable_fastapi.json +136 -0
  32. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/dvpwa.json +2 -2
  33. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/full-stack-fastapi-template.json +2 -2
  34. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/healthchecks.json +2 -2
  35. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/httpie.json +8 -2
  36. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/luigi.json +2 -2
  37. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/my_gdb.json +2 -2
  38. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/network-topologer.json +2 -2
  39. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/pygoat.json +8 -2
  40. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +2 -2
  41. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/vulnerable-flask.json +2 -2
  42. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/expected/yt-fts.json +2 -2
  43. coretrace_python_analyzer-0.4.0/tests/test_advisory_evidence.py +218 -0
  44. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_advisory_feeds.py +3 -2
  45. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_baseline.py +1 -1
  46. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_class_keywords_with_targets.py +2 -2
  47. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_closures.py +5 -5
  48. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_dependencies.py +12 -0
  49. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_discovery.py +1 -1
  50. coretrace_python_analyzer-0.4.0/tests/test_module_level.py +146 -0
  51. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_plugins.py +26 -3
  52. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_project.py +58 -1
  53. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_pyir_imports.py +1 -1
  54. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_release.py +1 -1
  55. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_remaining_limits.py +1 -1
  56. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_control_flow.py +2 -2
  57. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_coverage.py +4 -2
  58. coretrace_python_analyzer-0.3.0/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -56
  59. coretrace_python_analyzer-0.3.0/src/coretrace_python/dependency/correlation.py +0 -87
  60. coretrace_python_analyzer-0.3.0/tests/regression/expected/Vulnerable_fastapi.json +0 -46
  61. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/.github/workflows/ci.yml +0 -0
  62. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/.github/workflows/release.yml +0 -0
  63. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/.gitignore +0 -0
  64. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/LICENSE +0 -0
  65. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/NOTICE +0 -0
  66. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/README.md +0 -0
  67. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/docs/architecture.md +0 -0
  68. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/examples/add.py +0 -0
  69. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/examples/canonical_symbols.py +0 -0
  70. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/__main__.py +0 -0
  71. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/__init__.py +0 -0
  72. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/constants.py +0 -0
  73. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/heap.py +0 -0
  74. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/ranges.py +0 -0
  75. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/abstract/values.py +0 -0
  76. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/__init__.py +0 -0
  77. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/manager.py +0 -0
  78. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/analysis/provider.py +0 -0
  79. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  80. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  81. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  83. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  84. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  85. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  86. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  87. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  88. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  89. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  90. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  91. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  92. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  93. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  94. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  95. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  97. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  99. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  100. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  101. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  103. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  104. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  106. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  107. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  108. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  109. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  110. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  111. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  112. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  113. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  114. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  115. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  116. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  117. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  118. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  119. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  120. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  121. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  122. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  123. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  124. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  125. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  126. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  127. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  128. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  129. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  130. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  131. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  132. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  133. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  134. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  135. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  136. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  137. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  138. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  139. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  140. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  141. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  142. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  143. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  144. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  145. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  146. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/__init__.py +0 -0
  147. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/builder.py +0 -0
  148. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/dominance.py +0 -0
  149. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cfg/model.py +0 -0
  150. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/cli.py +0 -0
  151. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  152. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  153. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dataflow/solver.py +0 -0
  154. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/policy.py +0 -0
  155. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/dependency/sbom.py +0 -0
  156. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/__init__.py +0 -0
  157. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/baseline.py +0 -0
  158. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/coverage.py +0 -0
  159. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/model.py +0 -0
  160. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/refutation.py +0 -0
  161. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/findings/suppressions.py +0 -0
  162. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/__init__.py +0 -0
  163. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  164. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/frontend/parser.py +0 -0
  165. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/__init__.py +0 -0
  166. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/nodes.py +0 -0
  167. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/hir/visitors.py +0 -0
  168. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
  169. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
  170. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/__init__.py +0 -0
  171. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/defuse.py +0 -0
  172. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/model.py +0 -0
  173. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/printer.py +0 -0
  174. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/ir/ssa.py +0 -0
  175. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/detectors.py +0 -0
  176. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/manifest.py +0 -0
  177. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/registry.py +0 -0
  178. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/plugins/secrets.py +0 -0
  179. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/__init__.py +0 -0
  180. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/json_format.py +0 -0
  181. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/report.py +0 -0
  182. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/sarif.py +0 -0
  183. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/reporters/text.py +0 -0
  184. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/__init__.py +0 -0
  185. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/identity.py +0 -0
  186. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/imports.py +0 -0
  187. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/semantic/symbols.py +0 -0
  188. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/__init__.py +0 -0
  189. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/manager.py +0 -0
  190. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/source/model.py +0 -0
  191. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/__init__.py +0 -0
  192. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/models.py +0 -0
  193. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/src/coretrace_python/taint/routes.py +0 -0
  194. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  195. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  196. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  197. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  198. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/regression/repositories.toml +0 -0
  199. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_aiohttp_models.py +0 -0
  200. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_analysis_manager.py +0 -0
  201. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_architecture.py +0 -0
  202. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_audit_detectors.py +0 -0
  203. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_cache.py +0 -0
  204. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_cfg.py +0 -0
  205. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_cli.py +0 -0
  206. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_config_secrets_coverage.py +0 -0
  207. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_constants.py +0 -0
  208. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_correlation.py +0 -0
  209. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_credential_context.py +0 -0
  210. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_dataflow.py +0 -0
  211. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_detectors.py +0 -0
  212. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_django_http_clients.py +0 -0
  213. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_django_routes.py +0 -0
  214. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_docs.py +0 -0
  215. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_dominance.py +0 -0
  216. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_emit_ir.py +0 -0
  217. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_exceptions.py +0 -0
  218. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_factories_match_loop_else.py +0 -0
  219. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_fail_on.py +0 -0
  220. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_fastapi_cli_precision.py +0 -0
  221. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_frameworks.py +0 -0
  222. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_frontend_gaps.py +0 -0
  223. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_heap.py +0 -0
  224. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_hex_digests.py +0 -0
  225. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_hir_builder.py +0 -0
  226. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_imports.py +0 -0
  227. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_interprocedural.py +0 -0
  228. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_interprocedural_taint.py +0 -0
  229. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_ir.py +0 -0
  230. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_match_patterns.py +0 -0
  231. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_nonlocal_writeback.py +0 -0
  232. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_operator_sources.py +0 -0
  233. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_packaging.py +0 -0
  234. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_parallel.py +0 -0
  235. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_parser.py +0 -0
  236. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_plaintext_credentials.py +0 -0
  237. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_real_code_precision.py +0 -0
  238. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_redirect_targets.py +0 -0
  239. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_refutation.py +0 -0
  240. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_refutation_evidence.py +0 -0
  241. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_regression.py +0 -0
  242. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_relative_paths.py +0 -0
  243. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_reporters.py +0 -0
  244. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_scale.py +0 -0
  245. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_scopes.py +0 -0
  246. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_secrets_sbom.py +0 -0
  247. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_self_state.py +0 -0
  248. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_source_manager.py +0 -0
  249. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_ssa.py +0 -0
  250. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_suppressions.py +0 -0
  251. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_symbol_resolution.py +0 -0
  252. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_symbols.py +0 -0
  253. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntactic_rules.py +0 -0
  254. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_syntax_expressions.py +0 -0
  255. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_taint.py +0 -0
  256. {coretrace_python_analyzer-0.3.0 → coretrace_python_analyzer-0.4.0}/tests/test_tornado_bottle_models.py +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-09-24)
4
+
5
+ ### Analysis
6
+
7
+ - The module body and the bodies of top-level classes are analysed as the functions `<module>` and `Cls.<body>`: their calls are call sites, their values carry taint, and an advisory entry point called at import time or in a class body is reachable. Coverage counts them; the cache format is bumped so cached results gain their findings (#113).
8
+
9
+ ### Dependencies
10
+
11
+ - Advisories distinguish the `affected_symbols` a fix changed from the public `entry_points` through which they are reached, each justified and carrying `conditions` (checkable argument values, or `semantic` ones kept as pending review); `modules` names what the package installs. Every dependency finding records its evidence `level`: `declared`, `imported`, `reachable` or `exploitable` (#109).
12
+ - A call to an API affected by several advisories of the pinned release reports every one of them, not the first listed (#109).
13
+ - One advisory per identifier and package: a later contributor replaces an earlier one, so a curated feed refines the bundled sample, and the sample names the modules its packages install (#109).
14
+ - A version specifier may be a union of ranges separated by `||`, as an advisory over several release series needs (`>=4.2,<4.2.28 || >=5.2,<5.2.11`); the OSV import produces one such advisory per package instead of one per range (#112).
15
+
16
+ ### Plugins
17
+
18
+ - Every plugin module is registered in `sys.modules` before it runs, so a single-file plugin may define a dataclass under `from __future__ import annotations` (#105).
19
+ - `ProjectPlugin.refine(ctx, findings)` lets a project plugin reclassify the findings of a run: severity, confidence and added metadata only, with `refined_by` and the original values recorded; anything else raises `RefinementError` (#106).
20
+
3
21
  ## 0.3.0 (2026-09-22)
4
22
 
5
23
  ### Plugins
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.3.0
3
+ Version: 0.4.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
@@ -162,6 +162,38 @@ symbols, as the shipped `sample-advisories` plugin does. Requirements matching t
162
162
  reported, calls to the affected symbols become reachable vulnerabilities and tainted
163
163
  calls become exploitable ones.
164
164
 
165
+ ### Reclassifying findings: `ProjectPlugin.refine`
166
+
167
+ A project plugin may also implement `refine(ctx, findings)`, called once with every
168
+ finding of the run after all plugins have reported. It returns the same findings, in
169
+ the same order, and may only change their `severity` and `confidence` or add metadata:
170
+ the rule, the location, the message and the evidence a finding already carries are not
171
+ the refiner's to touch, and the engine rejects a refinement that changes or drops any
172
+ of them with a `RefinementError`. A reclassified finding records `refined_by` and the
173
+ `original_severity` or `original_confidence` it had, so a report always shows what the
174
+ detector said and who changed it.
175
+
176
+ ```python
177
+ from dataclasses import replace
178
+
179
+ from coretrace_python.findings import Confidence
180
+ from coretrace_python.plugins import ProjectContext, ProjectPlugin
181
+
182
+
183
+ class InternalOnly(ProjectPlugin):
184
+ name = "internal-only"
185
+
186
+ def analyze_project(self, ctx: ProjectContext):
187
+ return ()
188
+
189
+ def refine(self, ctx: ProjectContext, findings):
190
+ return tuple(
191
+ replace(f, confidence=Confidence.LOW, metadata={**f.metadata, "exposure": "internal"})
192
+ if f.function in INTERNAL else f
193
+ for f in findings
194
+ )
195
+ ```
196
+
165
197
  ### Secret scanners: `SecretDetector`
166
198
 
167
199
  Judges every string literal of the module, or every value of a configuration file,
@@ -71,7 +71,10 @@ its package (`app/views.py` is `app.views`). Hidden directories, `node_modules`,
71
71
  dependency graph. Configuration files under the root (`.env`, YAML, TOML, JSON, INI,
72
72
  properties) are scanned for secrets.
73
73
 
74
- Functions and methods are analysed; module-level statements outside functions are not.
74
+ Functions and methods are analysed, and so are the module body and the bodies of
75
+ top-level classes, as the functions `<module>` and `Cls.<body>`: `application =
76
+ get_asgi_application()` in `asgi.py` or `email = forms.EmailField()` in a form class are
77
+ calls the analyzer sees, and a module-level `os.system(input())` is a finding in `<module>`.
75
78
  A function using syntax outside the supported subset is reported as an
76
79
  `unsupported-syntax` note and the other functions are still analysed; a file Python
77
80
  itself cannot parse is reported as a `syntax-error`. The coverage line and the JSON
@@ -198,7 +201,7 @@ the directory of the checked file; a path outside it is printed as it is.
198
201
  ```json
199
202
  {
200
203
  "schema_version": 1,
201
- "tool": {"name": "coretrace-python-analyzer", "version": "0.3.0"},
204
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.4.0"},
202
205
  "root": "/home/me/project",
203
206
  "findings": [
204
207
  {
@@ -241,7 +244,8 @@ coretrace-python-analyzer --check src/ --advisories advisories.json
241
244
 
242
245
  A directory check reads `advisories.json` at the project root and every file passed
243
246
  with `--advisories`; a local entry wins over a plugin's for the same advisory. The file
244
- lists advisories with the package, the vulnerable version range and, optionally, the
247
+ lists advisories with the package, the vulnerable version range (a specifier, or several
248
+ separated by `||` when several release series are affected) and, optionally, the
245
249
  affected APIs that feed reachability and correlation:
246
250
 
247
251
  ```json
@@ -254,13 +258,45 @@ affected APIs that feed reachability and correlation:
254
258
  "vulnerable": "<5.4",
255
259
  "summary": "yaml.load can execute arbitrary code from untrusted documents",
256
260
  "severity": "critical",
257
- "affected_symbols": ["python.yaml.load", "python.yaml.full_load"],
261
+ "affected_symbols": ["python.yaml.constructor.FullConstructor.construct_python_object_apply"],
262
+ "entry_points": [
263
+ {
264
+ "symbol": "python.yaml.full_load",
265
+ "justification": "FullLoader uses FullConstructor (commit 5080ba5)",
266
+ "conditions": [
267
+ {"kind": "semantic", "text": "the document carries a python/object/apply tag"}
268
+ ]
269
+ },
270
+ {
271
+ "symbol": "python.yaml.load",
272
+ "justification": "same path when Loader is FullLoader",
273
+ "conditions": [
274
+ {"kind": "argument", "text": "Loader is FullLoader", "argument": "Loader", "values": ["python.yaml.FullLoader"]},
275
+ {"kind": "semantic", "text": "the document carries a python/object/apply tag"}
276
+ ]
277
+ }
278
+ ],
279
+ "modules": ["yaml"],
258
280
  "aliases": ["GHSA-8q59-q68h-6hv4"]
259
281
  }
260
282
  ]
261
283
  }
262
284
  ```
263
285
 
286
+ `affected_symbols` are the functions the fix changed; `entry_points` the public APIs
287
+ through which a project reaches them, each with the justification that ties it to the
288
+ fix — the commit, or the call path — and the `conditions` under which it is affected: an
289
+ argument value the engine can check, or a `semantic` condition it cannot yet, kept on the
290
+ finding as pending review rather than dropped. A call to either kind of symbol is
291
+ reachable. `modules` names the top-level modules the package installs, so the analyzer
292
+ can tell an imported package from a merely required one; it defaults to the package name.
293
+
294
+ Every dependency finding records the highest level of evidence established in its
295
+ `level` metadata: `declared` (the requirement allows a vulnerable version), `imported`
296
+ (a module of the package is imported somewhere), `reachable` (an entry point or affected
297
+ symbol is called) or `exploitable` (attacker input reaches it), with `entry_point`,
298
+ `justification`, `conditions` and `conditions_pending_review` on the last two.
299
+
264
300
  A `coretrace-policy.toml` at the root, or the file passed with `--policy`, denies
265
301
  packages, requires pins and lists accepted advisories whose findings are dropped:
266
302
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.3.0"
7
+ version = "0.4.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.3.0"
3
+ __version__ = "0.4.0"
4
4
 
@@ -0,0 +1,46 @@
1
+ """Calls, anywhere in the project, to an API affected by a vulnerable requirement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import ClassVar
7
+
8
+ from coretrace_python.analysis import AnyAnalysis
9
+ from coretrace_python.dependency import DependencyAnalysis
10
+ from coretrace_python.dependency.correlation import affected_symbols, evidence
11
+ from coretrace_python.findings import Confidence, Finding
12
+ from coretrace_python.interprocedural import CallGraphAnalysis, ExternalSymbol
13
+ from coretrace_python.plugins import ProjectContext, ProjectPlugin
14
+
15
+
16
+ class ReachableVulnerabilityPlugin(ProjectPlugin):
17
+ name: ClassVar[str] = "reachable-vulnerability"
18
+ requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({DependencyAnalysis, CallGraphAnalysis})
19
+
20
+ def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
21
+ affected = affected_symbols(ctx.dependencies, ctx.advisories)
22
+ if not affected:
23
+ return ()
24
+ findings: list[Finding] = []
25
+ for module in ctx.modules:
26
+ graph = ctx.call_graph(module)
27
+ for function in graph.functions:
28
+ for site in graph.sites(function):
29
+ if not isinstance(site.target, ExternalSymbol):
30
+ continue
31
+ for advisory in affected.get(site.target.symbol, ()):
32
+ findings.append(
33
+ Finding(
34
+ rule_id="reachable-vulnerability",
35
+ message=(
36
+ f"{advisory.id}: {site.target.symbol} is affected in the required "
37
+ f"{advisory.package} {advisory.vulnerable}: {advisory.summary}"
38
+ ),
39
+ severity=advisory.severity,
40
+ confidence=Confidence.HIGH,
41
+ span=site.location,
42
+ function=function,
43
+ metadata=evidence(advisory, site.target.symbol, "reachable"),
44
+ )
45
+ )
46
+ return findings
@@ -28,6 +28,7 @@ class SampleAdvisories(ModelPlugin):
28
28
  "yaml.load and full_load can execute arbitrary code from untrusted documents",
29
29
  Severity.CRITICAL,
30
30
  (_sym("yaml.load"), _sym("yaml.full_load"), _sym("yaml.unsafe_load")),
31
+ modules=("yaml",),
31
32
  ),
32
33
  Advisory(
33
34
  "CVE-2018-18074",
@@ -105,5 +106,6 @@ class SampleAdvisories(ModelPlugin):
105
106
  Severity.HIGH,
106
107
  (_sym("PIL.Image.open"),),
107
108
  ("GHSA-cfh3-3jmp-rvhc",),
109
+ modules=("PIL",),
108
110
  ),
109
111
  )
@@ -17,11 +17,13 @@ class VulnerableDependencyPlugin(ProjectPlugin):
17
17
 
18
18
  def analyze_project(self, ctx: ProjectContext) -> Sequence[Finding]:
19
19
  findings: list[Finding] = []
20
+ imported = [s for module in ctx.modules for s in ctx.imports(module).all_symbols()]
20
21
  for requirement in ctx.dependencies.requirements:
21
22
  for advisory in ctx.advisories:
22
23
  if not advisory.affects(requirement):
23
24
  continue
24
25
  pinned = requirement.pinned is not None
26
+ level = "imported" if advisory.imported_by(imported) else "declared"
25
27
  findings.append(
26
28
  Finding(
27
29
  rule_id="vulnerable-dependency",
@@ -37,6 +39,7 @@ class VulnerableDependencyPlugin(ProjectPlugin):
37
39
  "advisory": advisory.id,
38
40
  "package": advisory.package,
39
41
  "specifier": requirement.specifier,
42
+ "level": level,
40
43
  },
41
44
  )
42
45
  )
@@ -37,7 +37,7 @@ from coretrace_python.interprocedural import (
37
37
  from coretrace_python.semantic.symbols import SymbolId
38
38
  from coretrace_python.source import SourceId, SourceSpan
39
39
 
40
- CACHE_FORMAT = 4
40
+ CACHE_FORMAT = 5
41
41
 
42
42
 
43
43
  @dataclass(frozen=True)
@@ -11,6 +11,8 @@ from coretrace_python.dependency.advisories import (
11
11
  from coretrace_python.dependency.graph import (
12
12
  DEPENDENCY_FILES,
13
13
  Advisory,
14
+ AdvisoryEntryPoint,
15
+ Condition,
14
16
  DependencyAnalysis,
15
17
  DependencyGraph,
16
18
  Requirement,
@@ -26,7 +28,9 @@ __all__ = [
26
28
  "DEPENDENCY_FILES",
27
29
  "POLICY_FILE",
28
30
  "Advisory",
31
+ "AdvisoryEntryPoint",
29
32
  "AdvisoryFileError",
33
+ "Condition",
30
34
  "DependencyAnalysis",
31
35
  "DependencyGraph",
32
36
  "Policy",
@@ -5,8 +5,8 @@ OSV dump into ``Advisory`` values, keeping the PyPI ecosystem and turning each r
5
5
  events into a version specifier; ``dump_advisories`` writes them as a small JSON file
6
6
  that a project keeps at its root as ``advisories.json`` or passes with ``--advisories``.
7
7
  OSV records name no affected APIs, so imported advisories feed the requirement checks
8
- and the SBOM; a file completed by hand with ``affected_symbols`` also feeds the
9
- reachability and correlation checks.
8
+ and the SBOM; a file completed by hand with ``affected_symbols``, ``entry_points`` and
9
+ their ``conditions`` also feeds the reachability and correlation checks.
10
10
  """
11
11
 
12
12
  from __future__ import annotations
@@ -17,7 +17,7 @@ from collections.abc import Iterable, Iterator, Mapping
17
17
  from pathlib import Path
18
18
  from typing import Any
19
19
 
20
- from coretrace_python.dependency.graph import Advisory, normalize
20
+ from coretrace_python.dependency.graph import Advisory, AdvisoryEntryPoint, Condition, normalize
21
21
  from coretrace_python.findings import Severity
22
22
  from coretrace_python.semantic.symbols import SymbolId
23
23
 
@@ -84,8 +84,9 @@ def import_osv(records: Iterable[Mapping[str, Any]]) -> tuple[Advisory, ...]:
84
84
  if str(package.get("ecosystem", "")).lower() != "pypi" or not package.get("name"):
85
85
  continue
86
86
  name = normalize(str(package["name"]))
87
- for specifier in _specifiers(affected.get("ranges") or []):
88
- advisories.append(Advisory(identifier, name, specifier, summary, severity, (), aliases))
87
+ specifiers = _specifiers(affected.get("ranges") or [])
88
+ if specifiers:
89
+ advisories.append(Advisory(identifier, name, " || ".join(specifiers), summary, severity, (), aliases))
89
90
  return tuple(advisories)
90
91
 
91
92
 
@@ -137,6 +138,15 @@ def dump_advisories(advisories: Iterable[Advisory]) -> str:
137
138
  "severity": a.severity.value,
138
139
  "affected_symbols": [str(s) for s in a.affected_symbols],
139
140
  "aliases": list(a.aliases),
141
+ "entry_points": [
142
+ {
143
+ "symbol": str(e.symbol),
144
+ "justification": e.justification,
145
+ "conditions": [_condition_entry(c) for c in e.conditions],
146
+ }
147
+ for e in a.entry_points
148
+ ],
149
+ "modules": list(a.modules),
140
150
  }
141
151
  for a in advisories
142
152
  ],
@@ -144,6 +154,15 @@ def dump_advisories(advisories: Iterable[Advisory]) -> str:
144
154
  return json.dumps(document, indent=2) + "\n"
145
155
 
146
156
 
157
+ def _condition_entry(condition: Condition) -> dict[str, Any]:
158
+ entry: dict[str, Any] = {"kind": condition.kind, "text": condition.text}
159
+ if condition.argument is not None:
160
+ entry["argument"] = condition.argument
161
+ if condition.values:
162
+ entry["values"] = list(condition.values)
163
+ return entry
164
+
165
+
147
166
  def load_advisories(path: Path) -> tuple[Advisory, ...]:
148
167
  try:
149
168
  document = json.loads(path.read_text(encoding="utf-8"))
@@ -165,4 +184,22 @@ def _advisory(entry: Mapping[str, Any]) -> Advisory:
165
184
  Severity(entry["severity"]),
166
185
  tuple(SymbolId(str(s)) for s in entry.get("affected_symbols") or []),
167
186
  tuple(str(a) for a in entry.get("aliases") or []),
187
+ tuple(_entry_point(e) for e in entry.get("entry_points") or []),
188
+ tuple(str(m) for m in entry.get("modules") or []),
189
+ )
190
+
191
+
192
+ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
193
+ return AdvisoryEntryPoint(
194
+ SymbolId(str(entry["symbol"])),
195
+ str(entry["justification"]),
196
+ tuple(
197
+ Condition(
198
+ str(c["kind"]),
199
+ str(c["text"]),
200
+ None if c.get("argument") is None else str(c["argument"]),
201
+ tuple(str(v) for v in c.get("values") or []),
202
+ )
203
+ for c in entry.get("conditions") or []
204
+ ),
168
205
  )
@@ -0,0 +1,113 @@
1
+ """Correlation engine (architecture §27).
2
+
3
+ A package required in a vulnerable version, an API the advisory affects, a call to that
4
+ API reachable in the project, and attacker-controlled data reaching that call: the
5
+ affected APIs become sinks of the ``ADVISORY`` taint kind, so the shared taint engine,
6
+ the function summaries and the refutation engine do the work, and the flows they leave
7
+ are correlated here into one high-confidence ``exploitable-vulnerability`` finding.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Iterable, Mapping
13
+
14
+ from coretrace_python.dependency.graph import DIRECT, Advisory, DependencyGraph
15
+ from coretrace_python.findings import Confidence, Finding, Severity
16
+ from coretrace_python.findings.refutation import Status, Verdict, Verdicts
17
+ from coretrace_python.semantic.symbols import SymbolId
18
+ from coretrace_python.taint import Sink, TaintFlow, TaintKind
19
+
20
+ Affected = Mapping[SymbolId, tuple[Advisory, ...]]
21
+
22
+
23
+ def affected_symbols(dependencies: DependencyGraph, advisories: Iterable[Advisory]) -> Affected:
24
+ """The APIs affected by advisories whose package is required in a vulnerable version,
25
+ each with every such advisory: a call to ``yaml.load`` reaches every CVE of the
26
+ pinned release, not the first one listed."""
27
+
28
+ affected: dict[SymbolId, list[Advisory]] = {}
29
+ for requirement in dependencies.requirements:
30
+ for advisory in advisories:
31
+ if advisory.affects(requirement):
32
+ for symbol in advisory.reachable_symbols:
33
+ found = affected.setdefault(symbol, [])
34
+ if advisory not in found:
35
+ found.append(advisory)
36
+ return {symbol: tuple(found) for symbol, found in affected.items()}
37
+
38
+
39
+ def advisory_sinks(affected: Affected) -> tuple[Sink, ...]:
40
+ return tuple(Sink(symbol, TaintKind.ADVISORY) for symbol in affected)
41
+
42
+
43
+ def evidence(advisory: Advisory, symbol: SymbolId, level: str) -> dict[str, str]:
44
+ """What a finding keeps of the advisory for ``symbol``: the level of evidence
45
+ established, how the symbol relates to the vulnerability and under which
46
+ conditions, the ones the engine could not check listed as pending review."""
47
+
48
+ metadata = {"advisory": advisory.id, "package": advisory.package, "symbol": str(symbol), "level": level}
49
+ entry = advisory.entry_point(symbol)
50
+ if entry is None:
51
+ metadata["justification"] = DIRECT
52
+ return metadata
53
+ metadata["entry_point"] = str(entry.symbol)
54
+ metadata["justification"] = entry.justification
55
+ if entry.conditions:
56
+ metadata["conditions"] = "; ".join(c.text for c in entry.conditions)
57
+ # ponytail: no condition is checked yet, so every one awaits review; argument
58
+ # conditions get checked at the call site once call sites carry argument symbols.
59
+ metadata["conditions_pending_review"] = "; ".join(c.text for c in entry.conditions)
60
+ return metadata
61
+
62
+
63
+ def correlate(
64
+ function: str,
65
+ flows: Iterable[TaintFlow],
66
+ verdicts: Verdicts | None,
67
+ affected: Affected,
68
+ ) -> tuple[Finding, ...]:
69
+ """Exploitable-vulnerability findings for the non-refuted ADVISORY flows of a function,
70
+ one per advisory the sink is affected by."""
71
+
72
+ findings: list[Finding] = []
73
+ for flow in flows:
74
+ if not flow.kinds & TaintKind.ADVISORY:
75
+ continue
76
+ verdict = verdicts.verdict(flow) if verdicts is not None else None
77
+ if verdict is not None and verdict.status is Status.REFUTED:
78
+ continue
79
+ hotspot = verdict is not None and verdict.status is Status.HOTSPOT
80
+ findings.extend(
81
+ _exploitable(function, flow, advisory, verdict, hotspot) for advisory in affected.get(flow.sink.symbol, ())
82
+ )
83
+ return tuple(findings)
84
+
85
+
86
+ def _exploitable(
87
+ function: str, flow: TaintFlow, advisory: Advisory, verdict: Verdict | None, hotspot: bool
88
+ ) -> Finding:
89
+ message = (
90
+ f"{advisory.id}: {flow.source.label} input reaches {flow.sink.symbol}, affected in "
91
+ f"the required {advisory.package} {advisory.vulnerable}: {advisory.summary}"
92
+ )
93
+ metadata = {
94
+ **evidence(advisory, flow.sink.symbol, "exploitable"),
95
+ "source": str(flow.source.symbol),
96
+ "source_label": flow.source.label,
97
+ "verdict": "hotspot" if hotspot else "vulnerability",
98
+ }
99
+ if verdict is not None:
100
+ metadata["evidence"] = verdict.evidence
101
+ if flow.through is not None and flow.sink_location is not None:
102
+ message += f" through {flow.through}"
103
+ metadata["through"] = flow.through
104
+ metadata["sink_line"] = str(flow.sink_location.start_line)
105
+ return Finding(
106
+ rule_id="exploitable-vulnerability",
107
+ message=message,
108
+ severity=Severity.CRITICAL,
109
+ confidence=Confidence.MEDIUM if hotspot else Confidence.HIGH,
110
+ span=flow.location,
111
+ function=function,
112
+ metadata=metadata,
113
+ )
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import re
13
13
  import tomllib
14
- from collections.abc import Mapping
14
+ from collections.abc import Iterable, Mapping
15
15
  from dataclasses import dataclass
16
16
  from pathlib import PurePath
17
17
  from types import MappingProxyType
@@ -50,6 +50,13 @@ class Version:
50
50
  return self.parts + (0,) * (length - len(self.parts))
51
51
 
52
52
  def satisfies(self, specifier: str) -> bool:
53
+ """Whether this version is in ``specifier``: comma-separated clauses all hold, and
54
+ ``||`` separates alternatives, as an advisory over several release series needs
55
+ (``>=4.2,<4.2.28 || >=5.2,<5.2.11``)."""
56
+
57
+ return any(self._satisfies_all(alternative) for alternative in _alternatives(specifier))
58
+
59
+ def _satisfies_all(self, specifier: str) -> bool:
53
60
  for clause in [c.strip() for c in specifier.split(",") if c.strip()]:
54
61
  match = _CLAUSE.match(clause)
55
62
  if match is None:
@@ -91,12 +98,17 @@ class Version:
91
98
  return True
92
99
 
93
100
 
101
+ def _alternatives(specifier: str) -> list[str]:
102
+ return [a for a in specifier.split("||") if a.strip()] or [""]
103
+
104
+
94
105
  def _lower_bounds(specifier: str) -> list[Version]:
95
106
  bounds: list[Version] = []
96
- for clause in [c.strip() for c in specifier.split(",") if c.strip()]:
97
- match = _CLAUSE.match(clause)
98
- if match is not None and match.group(1) in (">=", ">", "~=", "^", "==", "==="):
99
- bounds.append(Version.parse(match.group(2)))
107
+ for alternative in _alternatives(specifier):
108
+ for clause in [c.strip() for c in alternative.split(",") if c.strip()]:
109
+ match = _CLAUSE.match(clause)
110
+ if match is not None and match.group(1) in (">=", ">", "~=", "^", "==", "==="):
111
+ bounds.append(Version.parse(match.group(2)))
100
112
  return bounds
101
113
 
102
114
 
@@ -130,6 +142,37 @@ class Requirement:
130
142
  return any(v.satisfies(self.specifier) and v.satisfies(vulnerable) for v in candidates)
131
143
 
132
144
 
145
+ @dataclass(frozen=True)
146
+ class Condition:
147
+ """What must hold for an entry point to reach, or exploit, the affected code: the
148
+ value of an argument, the loader or algorithm used, the format decoded, the kind of
149
+ input, a configuration. ``kind`` says whether the engine can check it: an
150
+ ``argument`` condition names the argument and the values that satisfy it; a
151
+ ``semantic`` condition cannot be checked yet and is reported as pending review."""
152
+
153
+ kind: str
154
+ text: str
155
+ argument: str | None = None
156
+ values: tuple[str, ...] = ()
157
+
158
+ @property
159
+ def checkable(self) -> bool:
160
+ return self.kind == "argument"
161
+
162
+
163
+ @dataclass(frozen=True)
164
+ class AdvisoryEntryPoint:
165
+ """A public API through which a project reaches an affected symbol, justified by the
166
+ fixing commit or by a call path, with the conditions under which it is affected."""
167
+
168
+ symbol: SymbolId
169
+ justification: str
170
+ conditions: tuple[Condition, ...] = ()
171
+
172
+
173
+ DIRECT = "affected symbol, changed by the fix"
174
+
175
+
133
176
  @dataclass(frozen=True)
134
177
  class Advisory:
135
178
  id: str
@@ -139,10 +182,36 @@ class Advisory:
139
182
  severity: Severity
140
183
  affected_symbols: tuple[SymbolId, ...] = ()
141
184
  aliases: tuple[str, ...] = ()
185
+ entry_points: tuple[AdvisoryEntryPoint, ...] = ()
186
+ # Top-level modules the package installs, to tell an imported package from a merely
187
+ # required one; defaults to the package name (``pyyaml`` installs ``yaml``: say so).
188
+ modules: tuple[str, ...] = ()
189
+
190
+ def __post_init__(self) -> None:
191
+ if not self.modules:
192
+ object.__setattr__(self, "modules", (normalize(self.package).replace("-", "_"),))
142
193
 
143
194
  def affects(self, requirement: Requirement) -> bool:
144
195
  return requirement.name == normalize(self.package) and requirement.may_match(self.vulnerable)
145
196
 
197
+ @property
198
+ def reachable_symbols(self) -> tuple[SymbolId, ...]:
199
+ """Every symbol a call to which reaches the vulnerability: the entry points and
200
+ the affected symbols themselves."""
201
+
202
+ return (*(e.symbol for e in self.entry_points), *self.affected_symbols)
203
+
204
+ def entry_point(self, symbol: SymbolId) -> AdvisoryEntryPoint | None:
205
+ return next((e for e in self.entry_points if e.symbol == symbol), None)
206
+
207
+ def imported_by(self, symbols: Iterable[SymbolId]) -> bool:
208
+ """Whether any of the ``symbols`` a project imports belongs to this package."""
209
+
210
+ prefixes = tuple(f"python.{m}" for m in self.modules)
211
+ return any(
212
+ s.canonical_name == p or s.canonical_name.startswith(p + ".") for s in symbols for p in prefixes
213
+ )
214
+
146
215
 
147
216
  class DependencyGraph:
148
217
  def __init__(self, requirements: Mapping[str, Requirement] | None = None, errors: tuple[str, ...] = ()) -> None: