coretrace-python-analyzer 0.8.0__tar.gz → 0.10.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 (268) hide show
  1. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/CHANGELOG.md +18 -0
  2. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/PKG-INFO +1 -1
  3. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/usage.md +45 -4
  4. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/pyproject.toml +1 -1
  5. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/__init__.py +1 -1
  6. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cli.py +7 -2
  7. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/__init__.py +2 -0
  8. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/advisories.py +36 -1
  9. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/correlation.py +4 -1
  10. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/graph.py +34 -1
  11. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/sbom.py +23 -3
  12. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/vex.py +8 -2
  13. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/engine.py +88 -27
  14. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/__init__.py +4 -0
  15. coretrace_python_analyzer-0.10.0/src/coretrace_python/findings/provenance.py +26 -0
  16. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/json_format.py +15 -5
  17. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/report.py +3 -1
  18. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/sarif.py +13 -2
  19. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/engine.py +17 -2
  20. coretrace_python_analyzer-0.10.0/tests/test_attacker_arguments.py +203 -0
  21. coretrace_python_analyzer-0.10.0/tests/test_provenance.py +232 -0
  22. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_release.py +1 -1
  23. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.github/workflows/ci.yml +0 -0
  24. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.github/workflows/release.yml +0 -0
  25. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/.gitignore +0 -0
  26. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/LICENSE +0 -0
  27. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/NOTICE +0 -0
  28. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/README.md +0 -0
  29. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/architecture.md +0 -0
  30. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/docs/plugins.md +0 -0
  31. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/examples/add.py +0 -0
  32. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/examples/canonical_symbols.py +0 -0
  33. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/__main__.py +0 -0
  34. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/__init__.py +0 -0
  35. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/constants.py +0 -0
  36. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/heap.py +0 -0
  37. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/ranges.py +0 -0
  38. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/abstract/values.py +0 -0
  39. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/__init__.py +0 -0
  40. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/manager.py +0 -0
  41. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/analysis/provider.py +0 -0
  42. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/dependency_policy/dependency_policy.py +0 -0
  43. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/dependency_policy/plugin.toml +0 -0
  44. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/plugin.toml +0 -0
  45. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/reachable_vulnerability/reachable_vulnerability.py +0 -0
  46. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/sample_advisories/plugin.toml +0 -0
  47. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py +0 -0
  48. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml +0 -0
  49. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py +0 -0
  50. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/aiohttp/aiohttp_models.py +0 -0
  51. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/aiohttp/plugin.toml +0 -0
  52. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/bottle/bottle_models.py +0 -0
  53. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/bottle/plugin.toml +0 -0
  54. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/cli/cli_models.py +0 -0
  55. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/cli/plugin.toml +0 -0
  56. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/credentials/credential_models.py +0 -0
  57. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/credentials/plugin.toml +0 -0
  58. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/db_drivers/db_driver_models.py +0 -0
  59. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/db_drivers/plugin.toml +0 -0
  60. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/django/django_models.py +0 -0
  61. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/django/plugin.toml +0 -0
  62. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/fastapi/fastapi_models.py +0 -0
  63. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/fastapi/plugin.toml +0 -0
  64. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/flask/flask_models.py +0 -0
  65. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/flask/plugin.toml +0 -0
  66. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/http_clients/http_client_models.py +0 -0
  67. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/http_clients/plugin.toml +0 -0
  68. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/python_stdlib/plugin.toml +0 -0
  69. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/python_stdlib/python_stdlib.py +0 -0
  70. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/sqlalchemy/plugin.toml +0 -0
  71. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py +0 -0
  72. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/tornado/plugin.toml +0 -0
  73. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/models/tornado/tornado_models.py +0 -0
  74. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/config_secrets/config_secrets.py +0 -0
  75. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/config_secrets/plugin.toml +0 -0
  76. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py +0 -0
  77. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/secrets/hardcoded_secrets/plugin.toml +0 -0
  78. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/command_injection/command_injection.py +0 -0
  79. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/command_injection/plugin.toml +0 -0
  80. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py +0 -0
  81. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/insecure_deserialization/plugin.toml +0 -0
  82. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/open_redirect/open_redirect.py +0 -0
  83. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/open_redirect/plugin.toml +0 -0
  84. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/path_traversal/path_traversal.py +0 -0
  85. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/path_traversal/plugin.toml +0 -0
  86. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/plaintext_credentials/plaintext_credentials.py +0 -0
  87. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/plaintext_credentials/plugin.toml +0 -0
  88. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/sql_injection/plugin.toml +0 -0
  89. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/sql_injection/sql_injection.py +0 -0
  90. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/ssrf/plugin.toml +0 -0
  91. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/ssrf/ssrf.py +0 -0
  92. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/xss/plugin.toml +0 -0
  93. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/security/xss/xss.py +0 -0
  94. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/dangerous_eval/dangerous_eval.py +0 -0
  95. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/dangerous_eval/plugin.toml +0 -0
  96. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/flask_debug/flask_debug.py +0 -0
  97. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/flask_debug/plugin.toml +0 -0
  98. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/insecure_tempfile.py +0 -0
  99. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tempfile/plugin.toml +0 -0
  100. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tls/insecure_tls.py +0 -0
  101. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/insecure_tls/plugin.toml +0 -0
  102. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/missing_timeout/missing_timeout.py +0 -0
  103. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/missing_timeout/plugin.toml +0 -0
  104. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_archive/plugin.toml +0 -0
  105. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_archive/unsafe_archive.py +0 -0
  106. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_xml/plugin.toml +0 -0
  107. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/unsafe_xml/unsafe_xml.py +0 -0
  108. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_crypto/plugin.toml +0 -0
  109. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_crypto/weak_crypto.py +0 -0
  110. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_random/plugin.toml +0 -0
  111. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/bundled/syntax/weak_random/weak_random.py +0 -0
  112. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cache.py +0 -0
  113. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/__init__.py +0 -0
  114. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/builder.py +0 -0
  115. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/dominance.py +0 -0
  116. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/cfg/model.py +0 -0
  117. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/__init__.py +0 -0
  118. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/lattice.py +0 -0
  119. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dataflow/solver.py +0 -0
  120. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/dependency/policy.py +0 -0
  121. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/baseline.py +0 -0
  122. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/coverage.py +0 -0
  123. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/model.py +0 -0
  124. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/refutation.py +0 -0
  125. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/findings/suppressions.py +0 -0
  126. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/__init__.py +0 -0
  127. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/ast_adapter.py +0 -0
  128. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/frontend/parser.py +0 -0
  129. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/__init__.py +0 -0
  130. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/nodes.py +0 -0
  131. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/hir/visitors.py +0 -0
  132. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/__init__.py +0 -0
  133. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/callgraph.py +0 -0
  134. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/modulegraph.py +0 -0
  135. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/interprocedural/summaries.py +0 -0
  136. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/__init__.py +0 -0
  137. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/defuse.py +0 -0
  138. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/lowering.py +0 -0
  139. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/model.py +0 -0
  140. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/printer.py +0 -0
  141. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/ir/ssa.py +0 -0
  142. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/__init__.py +0 -0
  143. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/api.py +0 -0
  144. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/detectors.py +0 -0
  145. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/loader.py +0 -0
  146. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/manifest.py +0 -0
  147. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/registry.py +0 -0
  148. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/plugins/secrets.py +0 -0
  149. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/__init__.py +0 -0
  150. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/reporters/text.py +0 -0
  151. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/__init__.py +0 -0
  152. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/identity.py +0 -0
  153. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/imports.py +0 -0
  154. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/scopes.py +0 -0
  155. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/semantic/symbols.py +0 -0
  156. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/__init__.py +0 -0
  157. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/manager.py +0 -0
  158. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/source/model.py +0 -0
  159. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/__init__.py +0 -0
  160. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/models.py +0 -0
  161. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/routes.py +0 -0
  162. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/src/coretrace_python/taint/templates.py +0 -0
  163. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Chatbot.json +0 -0
  164. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/DjangoGoat.json +0 -0
  165. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Flask-Image-Editing-Website.json +0 -0
  166. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/InsecureGram.json +0 -0
  167. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/MiniBookApiServer.json +0 -0
  168. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/NPL-IA.json +0 -0
  169. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Spam-Detection-Project.json +0 -0
  170. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/Vulnerable_fastapi.json +0 -0
  171. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/dvpwa.json +0 -0
  172. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/full-stack-fastapi-template.json +0 -0
  173. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/healthchecks.json +0 -0
  174. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/httpie.json +0 -0
  175. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/luigi.json +0 -0
  176. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/my_gdb.json +0 -0
  177. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/network-topologer.json +0 -0
  178. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/pygoat.json +0 -0
  179. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/uploadbox-cloud-storage-solution.json +0 -0
  180. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/vulnerable-flask.json +0 -0
  181. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/expected/yt-fts.json +0 -0
  182. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/README.md +0 -0
  183. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/app.py +0 -0
  184. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/requirements.txt +0 -0
  185. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/fixtures/vulnerable-flask/vulnerable_services.py +0 -0
  186. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/regression/repositories.toml +0 -0
  187. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_advisory_evidence.py +0 -0
  188. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_advisory_feeds.py +0 -0
  189. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_aiohttp_models.py +0 -0
  190. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_analysis_manager.py +0 -0
  191. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_architecture.py +0 -0
  192. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_argument_conditions.py +0 -0
  193. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_audit_detectors.py +0 -0
  194. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_baseline.py +0 -0
  195. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cache.py +0 -0
  196. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cfg.py +0 -0
  197. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_class_keywords_with_targets.py +0 -0
  198. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_cli.py +0 -0
  199. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_closures.py +0 -0
  200. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_config_secrets_coverage.py +0 -0
  201. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_constants.py +0 -0
  202. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_correlation.py +0 -0
  203. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_credential_context.py +0 -0
  204. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dataflow.py +0 -0
  205. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dependencies.py +0 -0
  206. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_detectors.py +0 -0
  207. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_discovery.py +0 -0
  208. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_http_clients.py +0 -0
  209. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_routes.py +0 -0
  210. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_django_safe_values.py +0 -0
  211. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_docs.py +0 -0
  212. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_dominance.py +0 -0
  213. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_emit_ir.py +0 -0
  214. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_exceptions.py +0 -0
  215. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_factories_match_loop_else.py +0 -0
  216. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_fail_on.py +0 -0
  217. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_fastapi_cli_precision.py +0 -0
  218. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_frameworks.py +0 -0
  219. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_frontend_gaps.py +0 -0
  220. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_heap.py +0 -0
  221. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_hex_digests.py +0 -0
  222. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_hir_builder.py +0 -0
  223. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_imports.py +0 -0
  224. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_interprocedural.py +0 -0
  225. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_interprocedural_taint.py +0 -0
  226. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ir.py +0 -0
  227. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_match_patterns.py +0 -0
  228. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_module_level.py +0 -0
  229. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_nonlocal_writeback.py +0 -0
  230. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_operator_sources.py +0 -0
  231. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_packaging.py +0 -0
  232. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_parallel.py +0 -0
  233. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_parser.py +0 -0
  234. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_plaintext_credentials.py +0 -0
  235. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_plugins.py +0 -0
  236. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project.py +0 -0
  237. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project_models.py +0 -0
  238. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_project_validators.py +0 -0
  239. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_pyir_imports.py +0 -0
  240. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_read_entry_points.py +0 -0
  241. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_real_code_precision.py +0 -0
  242. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_redirect_targets.py +0 -0
  243. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_refutation.py +0 -0
  244. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_refutation_evidence.py +0 -0
  245. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_regression.py +0 -0
  246. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_relative_paths.py +0 -0
  247. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_remaining_limits.py +0 -0
  248. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_reporters.py +0 -0
  249. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_safe_arguments.py +0 -0
  250. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_scale.py +0 -0
  251. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_scopes.py +0 -0
  252. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_secrets_sbom.py +0 -0
  253. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_self_state.py +0 -0
  254. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_source_manager.py +0 -0
  255. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ssa.py +0 -0
  256. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_ssrf_targets.py +0 -0
  257. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_summary_sanitizers.py +0 -0
  258. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_suppressions.py +0 -0
  259. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_symbol_resolution.py +0 -0
  260. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_symbols.py +0 -0
  261. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntactic_rules.py +0 -0
  262. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_control_flow.py +0 -0
  263. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_coverage.py +0 -0
  264. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_syntax_expressions.py +0 -0
  265. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_taint.py +0 -0
  266. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_template_escaping.py +0 -0
  267. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_tornado_bottle_models.py +0 -0
  268. {coretrace_python_analyzer-0.8.0 → coretrace_python_analyzer-0.10.0}/tests/test_vex.py +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 (2026-09-25)
4
+
5
+ ### Reports
6
+
7
+ - A directory check names what its result was produced with, besides the engine and the sources. It lists each plugin it loaded, by manifest name and version, and each advisory file it read, by path, each with the SHA-256 digest of its content. The formats show them in these places:
8
+ - JSON: `tool.components`;
9
+ - SARIF: `tool.extensions`;
10
+ - CycloneDX SBOM: `metadata.tools.components`, with their hashes;
11
+ - OpenVEX: the document's `tooling`, so its `@id` changes with the advisory data.
12
+
13
+ Every finding about an advisory names the plugin (`name@version`) or advisory file it comes from, in its `advisory_source` metadata. The cache key and the reports identify a plugin by the same digest (#149).
14
+
15
+ ## 0.9.0 (2026-09-25)
16
+
17
+ ### Precision
18
+
19
+ - An advisory entry point may name the `attacker_arguments` through which attacker input exploits it: by keyword, and by position when the argument may be passed positionally, a variadic one taking every later position. Attacker input in another argument leaves the call reachable instead of exploitable, so an advisory for `send_from_directory` that names its `path` no longer counts `download_name=`, and one for `requests.get` that names its `url` no longer counts the body. An argument unpacked with `*` or `**` may fill any of them: like an argument condition it leaves undecided, it does not rule the call out, so a wrapper forwarding `*args, **kwargs` still reaches the entry point. An entry point naming no argument counts every argument, as before. Each taint flow now records how its value is passed to the sink (#145).
20
+
3
21
  ## 0.8.0 (2026-09-25)
4
22
 
5
23
  ### Precision
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: coretrace-python-analyzer
3
- Version: 0.8.0
3
+ Version: 0.10.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
@@ -239,7 +239,7 @@ the directory of the checked file; a path outside it is printed as it is.
239
239
  ```json
240
240
  {
241
241
  "schema_version": 1,
242
- "tool": {"name": "coretrace-python-analyzer", "version": "0.8.0"},
242
+ "tool": {"name": "coretrace-python-analyzer", "version": "0.10.0"},
243
243
  "root": "/home/me/project",
244
244
  "findings": [
245
245
  {
@@ -261,10 +261,34 @@ the directory of the checked file; a path outside it is printed as it is.
261
261
 
262
262
  `root` is the directory the paths are relative to.
263
263
 
264
+ A directory check also lists, in `tool.components`, what the result was produced with
265
+ besides the engine and the sources: each plugin it loaded, by manifest name and version,
266
+ and each advisory file it read, by path (relative to the root when under it). Each
267
+ component carries the SHA-256 digest of its content, so a result can be traced to the
268
+ exact advisory data it used and reproduced. A plugin's digest covers its directory as the
269
+ cache fingerprints it, bytecode and hidden files left out; a file's digest covers its
270
+ bytes. A single-file check lists no components.
271
+
272
+ ```json
273
+ "tool": {
274
+ "name": "coretrace-python-analyzer", "version": "0.10.0",
275
+ "components": [
276
+ {"kind": "plugin", "name": "curated-advisories", "version": "2026.09.25", "digest": "sha256:…"},
277
+ {"kind": "advisories", "name": "advisories.json", "digest": "sha256:…"}
278
+ ]
279
+ }
280
+ ```
281
+
282
+ A finding about an advisory names, in its `advisory_source` metadata, where the advisory
283
+ comes from. For a plugin this is `name@version`, and for an advisory file its path. When
284
+ several sources define the same advisory, it names the one whose definition is used, as
285
+ the next section describes.
286
+
264
287
  `--format sarif` prints a SARIF 2.1.0 log, one run with the tool, its rules and one
265
288
  result per finding. The root is declared once as the `SRCROOT` original URI base and
266
289
  every location under it is relative to that base, which is what code scanning services
267
- need to attach results to files:
290
+ need to attach results to files. The components of a directory check are the tool's
291
+ `extensions`, their kind and digest in `properties`:
268
292
 
269
293
  ```bash
270
294
  coretrace-python-analyzer --check src/ --format sarif > report.sarif
@@ -336,6 +360,19 @@ it is reachable, called or not — `request.form['name']`, `for key in request.f
336
360
  `request.form.get('name')` all read it. A function reading it several times is reported
337
361
  once, where it first reads it.
338
362
 
363
+ Most vulnerabilities need attacker input in one argument: the path `send_from_directory`
364
+ serves, not its `download_name`; the URL `requests.get` fetches, not its body. An entry
365
+ point may name them in `attacker_arguments`, by keyword `argument` and, when it may be
366
+ passed positionally, by `position`, counted as for argument conditions; `"variadic": true`
367
+ extends a position to every later one, as `safe_join(directory, *pathnames)` takes
368
+ (`{"position": 1, "variadic": true}`). A call is then exploitable only when attacker
369
+ input reaches one of them, `"attacker_arguments": [{"argument": "url", "position": 0}]`
370
+ for `requests.get`; attacker input in another argument leaves it reachable. An argument
371
+ unpacked with `*args` or `**kwargs` may fill any of them: like an argument condition it
372
+ leaves undecided, it does not rule the call out, so it counts, and a wrapper forwarding
373
+ `*args, **kwargs` to the entry point still reaches it. An entry point without
374
+ `attacker_arguments` is exploitable through any argument.
375
+
339
376
  An `argument` condition is decided at each call. It names the argument by keyword and,
340
377
  when it may be passed positionally, by `position` (counted from 0, the receiver of a
341
378
  method excluded); `values` are what makes the call affected, written as the analyzer
@@ -372,7 +409,9 @@ ignore = ["CVE-2020-1747"]
372
409
  ```
373
410
 
374
411
  `--sbom PATH` writes a CycloneDX 1.5 bill of materials: one component per requirement
375
- with its package URL, and the advisories affecting them as vulnerabilities.
412
+ with its package URL, and the advisories affecting them as vulnerabilities. Its tools
413
+ list the analyzer and the components of the check, each with its SHA-256 hash: a plugin
414
+ as an `application`, an advisory file as `data`.
376
415
 
377
416
  ```bash
378
417
  coretrace-python-analyzer --check src/ --sbom sbom.json --policy security/policy.toml
@@ -394,7 +433,9 @@ not the code of installed packages. A framework calling the vulnerable function
394
433
  project's behalf would go unseen. Without a lock file, a package is never
395
434
  `not_affected`. The document's `@id` derives from its statements, so two runs with the
396
435
  same result share it; its `author` is `Unknown Author`, as OpenVEX tools write when they
397
- cannot know it.
436
+ cannot know it. Its `tooling` names the analyzer and each component with its digest
437
+ (`coretrace-python-analyzer 0.10.0; curated-advisories 2026.09.25 (sha256:…)`), so the
438
+ `@id` changes with the advisory data a statement was decided with.
398
439
 
399
440
  ## Large projects
400
441
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coretrace-python-analyzer"
7
- version = "0.8.0"
7
+ version = "0.10.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.8.0"
3
+ __version__ = "0.10.0"
4
4
 
@@ -242,9 +242,12 @@ def main(argv: list[str] | None = None) -> int:
242
242
  findings = analysis.findings
243
243
  coverage = analysis.coverage
244
244
  suppressed = analysis.suppressed
245
+ components = analysis.components
245
246
  if args.sbom is not None:
246
247
  args.sbom.write_text(
247
- render_sbom(analysis.dependencies, analysis.advisories, engine.TOOL_NAME, __version__),
248
+ render_sbom(
249
+ analysis.dependencies, analysis.advisories, engine.TOOL_NAME, __version__, analysis.components
250
+ ),
248
251
  encoding="utf-8",
249
252
  )
250
253
  if args.vex is not None:
@@ -258,6 +261,7 @@ def main(argv: list[str] | None = None) -> int:
258
261
  engine.TOOL_NAME,
259
262
  __version__,
260
263
  datetime.now(UTC),
264
+ analysis.components,
261
265
  ),
262
266
  encoding="utf-8",
263
267
  )
@@ -265,6 +269,7 @@ def main(argv: list[str] | None = None) -> int:
265
269
  file_analysis = engine.analyze_file(SourceManager().load_file(args.path), plugin_roots)
266
270
  findings, coverage = file_analysis.findings, file_analysis.coverage
267
271
  suppressed = file_analysis.suppressed
272
+ components = ()
268
273
  root = args.path.resolve() if args.path.is_dir() else args.path.resolve().parent
269
274
  baselined = None
270
275
  if args.baseline is not None:
@@ -273,7 +278,7 @@ def main(argv: list[str] | None = None) -> int:
273
278
  else:
274
279
  Baseline.of(findings, root).save(args.baseline)
275
280
  findings, baselined = (), findings
276
- report = engine.report(findings, coverage, root, suppressed, baselined)
281
+ report = engine.report(findings, coverage, root, suppressed, baselined, components)
277
282
  print(render(args.format or "text", report), end="")
278
283
  threshold = Severity(args.fail_on).rank if args.fail_on is not None else 0
279
284
  failing = any(finding.severity.rank >= threshold for finding in findings)
@@ -12,6 +12,7 @@ from coretrace_python.dependency.graph import (
12
12
  DEPENDENCY_FILES,
13
13
  Advisory,
14
14
  AdvisoryEntryPoint,
15
+ AttackerArgument,
15
16
  Condition,
16
17
  DependencyAnalysis,
17
18
  DependencyGraph,
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "Advisory",
32
33
  "AdvisoryEntryPoint",
33
34
  "AdvisoryFileError",
35
+ "AttackerArgument",
34
36
  "Condition",
35
37
  "DependencyAnalysis",
36
38
  "DependencyGraph",
@@ -17,7 +17,13 @@ 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, AdvisoryEntryPoint, Condition, normalize
20
+ from coretrace_python.dependency.graph import (
21
+ Advisory,
22
+ AdvisoryEntryPoint,
23
+ AttackerArgument,
24
+ Condition,
25
+ normalize,
26
+ )
21
27
  from coretrace_python.findings import Severity
22
28
  from coretrace_python.semantic.symbols import SymbolId
23
29
 
@@ -155,6 +161,19 @@ def _entry_point_entry(entry_point: AdvisoryEntryPoint) -> dict[str, Any]:
155
161
  }
156
162
  if entry_point.read:
157
163
  entry["read"] = True
164
+ if entry_point.attacker_arguments:
165
+ entry["attacker_arguments"] = [_attacker_argument_entry(a) for a in entry_point.attacker_arguments]
166
+ return entry
167
+
168
+
169
+ def _attacker_argument_entry(argument: AttackerArgument) -> dict[str, Any]:
170
+ entry: dict[str, Any] = {}
171
+ if argument.argument is not None:
172
+ entry["argument"] = argument.argument
173
+ if argument.position is not None:
174
+ entry["position"] = argument.position
175
+ if argument.variadic:
176
+ entry["variadic"] = True
158
177
  return entry
159
178
 
160
179
 
@@ -206,9 +225,25 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
206
225
  str(entry["justification"]),
207
226
  tuple(_condition(c) for c in entry.get("conditions") or []),
208
227
  read,
228
+ tuple(_attacker_argument(a) for a in entry.get("attacker_arguments") or []),
209
229
  )
210
230
 
211
231
 
232
+ def _attacker_argument(entry: Mapping[str, Any]) -> AttackerArgument:
233
+ argument, position, variadic = entry.get("argument"), entry.get("position"), entry.get("variadic", False)
234
+ if argument is not None and not isinstance(argument, str):
235
+ raise TypeError(f"attacker argument name must be a string, got {argument!r}")
236
+ if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
237
+ raise TypeError(f"attacker argument position must be a non-negative integer, got {position!r}")
238
+ if not isinstance(variadic, bool):
239
+ raise TypeError(f"attacker argument variadic must be true or false, got {variadic!r}")
240
+ if argument is None and position is None:
241
+ raise ValueError("an attacker argument needs a name, a position or both")
242
+ if variadic and position is None:
243
+ raise ValueError("a variadic attacker argument needs the position it starts at")
244
+ return AttackerArgument(argument, position, variadic)
245
+
246
+
212
247
  def _condition(entry: Mapping[str, Any]) -> Condition:
213
248
  position = entry.get("position")
214
249
  if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
@@ -133,7 +133,10 @@ def correlate(
133
133
  continue
134
134
  hotspot = verdict is not None and verdict.status is Status.HOTSPOT
135
135
  for advisory in affected.get(flow.sink.symbol, ()):
136
- check = check_conditions(advisory.entry_point(flow.sink.symbol), flow.sink_arguments)
136
+ entry = advisory.entry_point(flow.sink.symbol)
137
+ if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
138
+ continue
139
+ check = check_conditions(entry, flow.sink_arguments)
137
140
  if check.contradicted is None:
138
141
  findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
139
142
  return tuple(findings)
@@ -166,6 +166,31 @@ class Condition:
166
166
  return self.kind == "argument"
167
167
 
168
168
 
169
+ @dataclass(frozen=True)
170
+ class AttackerArgument:
171
+ """An argument of an entry point that must carry the attacker's input: passed by
172
+ keyword ``argument``, or at ``position`` (from 0, a method's receiver excluded) when it
173
+ may be passed positionally; a ``variadic`` one, such as ``*pathnames``, also takes every
174
+ later position."""
175
+
176
+ argument: str | None = None
177
+ position: int | None = None
178
+ variadic: bool = False
179
+
180
+ def receives(self, position: int | None, keyword: str | None) -> bool:
181
+ """Whether the argument passed at ``position`` or as ``keyword`` is this one. An
182
+ argument unpacked with ``*`` or ``**`` has neither and may fill this one: like an
183
+ argument condition it leaves undecided, it does not rule the call out, so it is."""
184
+
185
+ if keyword is not None:
186
+ return keyword == self.argument
187
+ if position is None:
188
+ return True
189
+ if self.position is None:
190
+ return False
191
+ return position == self.position or (self.variadic and position > self.position)
192
+
193
+
169
194
  @dataclass(frozen=True)
170
195
  class AdvisoryEntryPoint:
171
196
  """A public API through which a project reaches an affected symbol, justified by the
@@ -173,12 +198,20 @@ class AdvisoryEntryPoint:
173
198
 
174
199
  An entry point is reached by a call, or — when ``read`` is set, for an attribute whose
175
200
  getter runs the affected code, such as a lazily parsed request body — by any read of
176
- it or of an attribute of it."""
201
+ it or of an attribute of it. When it names ``attacker_arguments``, attacker input
202
+ exploits it only through one of them; otherwise through any argument."""
177
203
 
178
204
  symbol: SymbolId
179
205
  justification: str
180
206
  conditions: tuple[Condition, ...] = ()
181
207
  read: bool = False
208
+ attacker_arguments: tuple[AttackerArgument, ...] = ()
209
+
210
+ def exploitable_through(self, position: int | None, keyword: str | None) -> bool:
211
+ """Whether attacker input in the argument at ``position`` or ``keyword`` can
212
+ exploit the vulnerability."""
213
+
214
+ return not self.attacker_arguments or any(a.receives(position, keyword) for a in self.attacker_arguments)
182
215
 
183
216
 
184
217
  DIRECT = "affected symbol, changed by the fix"
@@ -3,9 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- from collections.abc import Iterable
6
+ from collections.abc import Iterable, Sequence
7
7
 
8
8
  from coretrace_python.dependency.graph import Advisory, DependencyGraph, Requirement
9
+ from coretrace_python.findings import PLUGIN, Component
9
10
 
10
11
  SPEC_VERSION = "1.5"
11
12
 
@@ -33,8 +34,22 @@ def _component(requirement: Requirement) -> dict[str, object]:
33
34
  return component
34
35
 
35
36
 
37
+ def _tool(component: Component) -> dict[str, object]:
38
+ """A plugin as an application, an advisory file as data, each with its SHA-256."""
39
+
40
+ tool: dict[str, object] = {"type": "application" if component.kind == PLUGIN else "data", "name": component.name}
41
+ if component.version is not None:
42
+ tool["version"] = component.version
43
+ tool["hashes"] = [{"alg": "SHA-256", "content": component.digest.removeprefix("sha256:")}]
44
+ return tool
45
+
46
+
36
47
  def render_sbom(
37
- dependencies: DependencyGraph, advisories: Iterable[Advisory], tool_name: str, tool_version: str
48
+ dependencies: DependencyGraph,
49
+ advisories: Iterable[Advisory],
50
+ tool_name: str,
51
+ tool_version: str,
52
+ components: Sequence[Component] = (),
38
53
  ) -> str:
39
54
  """A CycloneDX JSON document: one component per requirement, and the advisories
40
55
  affecting them as vulnerabilities. Deterministic for a given graph."""
@@ -57,7 +72,12 @@ def render_sbom(
57
72
  "specVersion": SPEC_VERSION,
58
73
  "version": 1,
59
74
  "metadata": {
60
- "tools": {"components": [{"type": "application", "name": tool_name, "version": tool_version}]}
75
+ "tools": {
76
+ "components": [
77
+ {"type": "application", "name": tool_name, "version": tool_version},
78
+ *(_tool(component) for component in components),
79
+ ]
80
+ }
61
81
  },
62
82
  "components": [_component(requirement) for requirement in requirements],
63
83
  "vulnerabilities": vulnerabilities,
@@ -23,7 +23,7 @@ from urllib.parse import quote
23
23
 
24
24
  from coretrace_python.dependency.graph import Advisory, DependencyGraph, Requirement
25
25
  from coretrace_python.dependency.sbom import purl
26
- from coretrace_python.findings import Finding
26
+ from coretrace_python.findings import Component, Finding
27
27
  from coretrace_python.findings.coverage import Coverage
28
28
 
29
29
  CONTEXT = "https://openvex.dev/ns/v0.2.0"
@@ -44,6 +44,7 @@ def render_vex(
44
44
  tool_name: str,
45
45
  tool_version: str,
46
46
  timestamp: datetime,
47
+ components: Sequence[Component] = (),
47
48
  ) -> str:
48
49
  """The OpenVEX document of the project at ``root``. ``evidence`` is every finding of
49
50
  the check, those suppressed or accepted by the policy included. The document's
@@ -69,7 +70,12 @@ def render_vex(
69
70
  **_status(advisory, requirement, about, dependencies, coverage, root),
70
71
  }
71
72
  )
72
- tooling = f"{tool_name} {tool_version}"
73
+ tooling = "; ".join(
74
+ (
75
+ f"{tool_name} {tool_version}",
76
+ *(" ".join(filter(None, (c.name, c.version, f"({c.digest})"))) for c in components),
77
+ )
78
+ )
73
79
  content = {
74
80
  "@context": CONTEXT,
75
81
  "author": AUTHOR,
@@ -7,9 +7,10 @@ plugins and analyses never import it.
7
7
  from __future__ import annotations
8
8
 
9
9
  import concurrent.futures
10
+ import hashlib
10
11
  import multiprocessing
11
12
  from collections.abc import Callable, Iterable, Mapping, Sequence
12
- from dataclasses import dataclass, field
13
+ from dataclasses import dataclass, field, replace
13
14
  from pathlib import Path
14
15
  from types import MappingProxyType
15
16
  from typing import Any, ClassVar
@@ -55,7 +56,10 @@ from coretrace_python.dependency.correlation import (
55
56
  correlate,
56
57
  )
57
58
  from coretrace_python.findings import (
59
+ ADVISORIES,
58
60
  FINDING_SCHEMA_VERSION,
61
+ PLUGIN,
62
+ Component,
59
63
  Confidence,
60
64
  Coverage,
61
65
  FileCoverage,
@@ -217,6 +221,8 @@ class ProjectAnalysis:
217
221
  suppressed: tuple[Finding, ...] = ()
218
222
  # Findings about an advisory the policy accepts, still evidence of what is reached.
219
223
  accepted: tuple[Finding, ...] = ()
224
+ # The plugins and advisory files the result was produced with.
225
+ components: tuple[Component, ...] = ()
220
226
 
221
227
 
222
228
  @dataclass(frozen=True)
@@ -358,10 +364,10 @@ def analyze_project(
358
364
  for error in dependencies.errors:
359
365
  findings.append(_note("syntax-error", error, sources.add_source(error.split(":")[0], ""), 1))
360
366
  advisory_paths = _advisory_paths(root, advisory_files)
361
- file_advisories: list[Advisory] = []
367
+ file_advisories: list[tuple[Path, tuple[Advisory, ...]]] = []
362
368
  for path in advisory_paths:
363
369
  try:
364
- file_advisories.extend(load_advisories(path))
370
+ file_advisories.append((path, load_advisories(path)))
365
371
  except AdvisoryFileError as error:
366
372
  findings.append(_note("syntax-error", str(error), sources.add_source(str(path), ""), 1))
367
373
  policy = Policy()
@@ -392,9 +398,8 @@ def analyze_project(
392
398
  probe = next(iter(managers.values()), None) or _register_all(build_hir(sources.add_source("<empty>", "")))
393
399
  registry = load_plugins(plugin_roots, probe)
394
400
  all_plugins: tuple[Plugin, ...] = (*(loaded.plugin for loaded in registry), *plugins)
395
- advisories = _merge_advisories(
396
- (a for plugin in all_plugins for a in plugin.advisories), file_advisories
397
- )
401
+ components = _components(registry, root, (path for path, _ in file_advisories))
402
+ advisories, origins = _merge_advisories(_contributions(registry, plugins, root, file_advisories))
398
403
  affected = affected_symbols(dependencies, advisories)
399
404
  models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
400
405
  for manager in managers.values():
@@ -425,7 +430,7 @@ def analyze_project(
425
430
  manager.provide(EscapedTemplates, escaped)
426
431
  manager.provide(ClearingAnalysis, clearing)
427
432
 
428
- configuration = _configuration_key(registry, plugins, models, advisories, dependencies, routes, escaped)
433
+ configuration = _configuration_key(components, plugins, models, advisories, dependencies, routes, escaped)
429
434
  keys = module_keys(
430
435
  graph,
431
436
  {name: fingerprint(configuration, str(files[name].source_id), name, files[name].text) for name in analysable},
@@ -511,6 +516,7 @@ def analyze_project(
511
516
  for plugin in all_plugins:
512
517
  if isinstance(plugin, ProjectPlugin):
513
518
  findings = list(apply_refinement(plugin, findings, plugin.refine(context, tuple(findings))))
519
+ findings = [_sourced(finding, origins) for finding in findings]
514
520
  accepted = tuple(f for f in findings if policy.accepts(f))
515
521
  kept, suppressed = partition(apply_policy(policy, findings), _text_of(sources))
516
522
  return ProjectAnalysis(
@@ -524,20 +530,78 @@ def analyze_project(
524
530
  Coverage(tuple(sorted(coverage, key=lambda c: c.path))),
525
531
  suppressed,
526
532
  accepted,
533
+ components,
534
+ )
535
+
536
+
537
+ Origins = Mapping[tuple[str, str], str]
538
+
539
+
540
+ def _merge_advisories(contributions: Iterable[tuple[str, Iterable[Advisory]]]) -> tuple[tuple[Advisory, ...], Origins]:
541
+ """One advisory per identifier and package, with the contributor it comes from: a
542
+ later contributor replaces an earlier one, so a curated plugin refines a bundled
543
+ sample and a local file, the project's own feed, wins over every plugin."""
544
+
545
+ merged: dict[tuple[str, str], tuple[str, Advisory]] = {}
546
+ for source, advisories in contributions:
547
+ for advisory in advisories:
548
+ merged[(advisory.id, advisory.package)] = (source, advisory)
549
+ return (
550
+ tuple(advisory for _, advisory in merged.values()),
551
+ MappingProxyType({key: source for key, (source, _) in merged.items()}),
527
552
  )
528
553
 
529
554
 
530
- def _merge_advisories(
531
- from_plugins: Iterable[Advisory], from_files: Iterable[Advisory]
532
- ) -> tuple[Advisory, ...]:
533
- """One advisory per identifier and package: a later contributor replaces an earlier
534
- one, so a curated plugin refines a bundled sample and a local file, the project's
535
- own feed, wins over every plugin."""
555
+ def _contributions(
556
+ registry: PluginRegistry,
557
+ plugins: Sequence[Plugin],
558
+ root: Path,
559
+ files: Iterable[tuple[Path, tuple[Advisory, ...]]],
560
+ ) -> list[tuple[str, Iterable[Advisory]]]:
561
+ """The advisories of each plugin, then of each advisory file, each under the name a
562
+ finding cites it by: ``name@version`` for a loaded plugin, the path for a file."""
563
+
564
+ return [
565
+ *((f"{loaded.manifest.name}@{loaded.manifest.version}", loaded.plugin.advisories) for loaded in registry),
566
+ *((plugin.name, plugin.advisories) for plugin in plugins),
567
+ *((_located(path, root), advisories) for path, advisories in files),
568
+ ]
536
569
 
537
- merged: dict[tuple[str, str], Advisory] = {}
538
- for advisory in (*from_plugins, *from_files):
539
- merged[(advisory.id, advisory.package)] = advisory
540
- return tuple(merged.values())
570
+
571
+ def _components(registry: PluginRegistry, root: Path, files: Iterable[Path]) -> tuple[Component, ...]:
572
+ """The plugins and advisory files a project result is produced with, each with the
573
+ digest of its content: the plugin directory as the cache fingerprints it, the file's
574
+ bytes."""
575
+
576
+ return (
577
+ *(
578
+ Component(PLUGIN, loaded.manifest.name, f"sha256:{directory_fingerprint(loaded.directory)}", loaded.manifest.version)
579
+ for loaded in registry
580
+ ),
581
+ *(
582
+ Component(ADVISORIES, _located(path, root), f"sha256:{hashlib.sha256(path.read_bytes()).hexdigest()}")
583
+ for path in files
584
+ ),
585
+ )
586
+
587
+
588
+ def _located(path: Path, root: Path) -> str:
589
+ """``path`` relative to ``root``, POSIX style, when it lies under it."""
590
+
591
+ try:
592
+ return path.resolve().relative_to(root.resolve()).as_posix()
593
+ except ValueError:
594
+ return str(path)
595
+
596
+
597
+ def _sourced(finding: Finding, origins: Origins) -> Finding:
598
+ """``finding`` with the plugin or advisory file its advisory comes from, when it
599
+ cites one."""
600
+
601
+ origin = origins.get((finding.metadata.get("advisory", ""), finding.metadata.get("package", "")))
602
+ if origin is None:
603
+ return finding
604
+ return replace(finding, metadata={**finding.metadata, "advisory_source": origin})
541
605
 
542
606
 
543
607
  def _advisory_paths(root: Path, advisory_files: Sequence[Path]) -> tuple[Path, ...]:
@@ -623,15 +687,13 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
623
687
  registry = load_plugins(batch.plugin_roots, next(iter(managers.values())))
624
688
  all_plugins: tuple[Plugin, ...] = (*(loaded.plugin for loaded in registry), *batch.plugins)
625
689
  dependencies = resolve_dependencies(batch.root, sources)
626
- file_advisories: list[Advisory] = []
690
+ file_advisories: list[tuple[Path, tuple[Advisory, ...]]] = []
627
691
  for path in batch.advisory_paths:
628
692
  try:
629
- file_advisories.extend(load_advisories(path))
693
+ file_advisories.append((path, load_advisories(path)))
630
694
  except AdvisoryFileError:
631
695
  continue
632
- advisories = _merge_advisories(
633
- (a for plugin in all_plugins for a in plugin.advisories), file_advisories
634
- )
696
+ advisories, _ = _merge_advisories(_contributions(registry, batch.plugins, batch.root, file_advisories))
635
697
  affected = affected_symbols(dependencies, advisories)
636
698
  models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
637
699
  routes = _decode_routes(batch.routes)
@@ -663,7 +725,7 @@ def _decode_routes(data: tuple[tuple[str, str, str, int], ...]) -> dict[SymbolId
663
725
 
664
726
 
665
727
  def _configuration_key(
666
- registry: PluginRegistry,
728
+ components: Sequence[Component],
667
729
  plugins: Sequence[Plugin],
668
730
  models: ModelTable,
669
731
  advisories: tuple[Advisory, ...],
@@ -679,10 +741,7 @@ def _configuration_key(
679
741
  str(PYIR_SCHEMA_VERSION),
680
742
  str(FINDING_SCHEMA_VERSION),
681
743
  str(PLUGIN_API_VERSION),
682
- *(
683
- f"{loaded.manifest.name}={loaded.manifest.version}:{directory_fingerprint(loaded.directory)}"
684
- for loaded in registry
685
- ),
744
+ *(f"{c.name}={c.version}:{c.digest}" for c in components if c.kind == PLUGIN),
686
745
  *(f"{type(p).__module__}.{type(p).__qualname__}" for p in plugins),
687
746
  repr(models),
688
747
  repr(advisories),
@@ -776,6 +835,7 @@ def report(
776
835
  root: Path | None = None,
777
836
  suppressed: Sequence[Finding] = (),
778
837
  baselined: Sequence[Finding] | None = None,
838
+ components: Sequence[Component] = (),
779
839
  ) -> Report:
780
840
  """The report of a check; paths under ``root`` are rendered relative to it.
781
841
  ``baselined`` is the findings a baseline accounts for, ``None`` when none applied."""
@@ -789,4 +849,5 @@ def report(
789
849
  tuple(suppressed),
790
850
  tuple(baselined or ()),
791
851
  baselined is not None,
852
+ tuple(components),
792
853
  )
@@ -2,9 +2,13 @@
2
2
 
3
3
  from coretrace_python.findings.coverage import Coverage, FileCoverage
4
4
  from coretrace_python.findings.model import FINDING_SCHEMA_VERSION, Confidence, Finding, Severity
5
+ from coretrace_python.findings.provenance import ADVISORIES, PLUGIN, Component
5
6
 
6
7
  __all__ = [
8
+ "ADVISORIES",
7
9
  "FINDING_SCHEMA_VERSION",
10
+ "PLUGIN",
11
+ "Component",
8
12
  "Confidence",
9
13
  "Coverage",
10
14
  "FileCoverage",
@@ -0,0 +1,26 @@
1
+ """What a result was produced with, besides the engine and the analysed sources.
2
+
3
+ Advisories decide the dependency findings and the VEX statements, and they come from
4
+ plugins and advisory files that change on their own schedule. A report names each of
5
+ them with the digest of its content, so a result can be traced to its data and
6
+ reproduced.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ PLUGIN = "plugin"
14
+ ADVISORIES = "advisories"
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Component:
19
+ """A loaded plugin, by its manifest ``name`` and ``version``, or an advisory file, by
20
+ its path as ``name``. ``digest`` is ``sha256:`` and the hex digest of its content:
21
+ the plugin directory as the cache fingerprints it, or the file's bytes."""
22
+
23
+ kind: str
24
+ name: str
25
+ digest: str
26
+ version: str | None = None