SafeAI-Static-Analyzer 2.4.0__tar.gz → 2.5.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 (249) hide show
  1. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/PKG-INFO +7 -2
  2. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/README.md +6 -1
  3. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/PKG-INFO +7 -2
  4. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/SOURCES.txt +15 -0
  5. safeai_static_analyzer-2.5.0/safeai/analysis/iac_correlation.py +482 -0
  6. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/orchestrator.py +42 -0
  7. safeai_static_analyzer-2.5.0/safeai/iac/__init__.py +169 -0
  8. safeai_static_analyzer-2.5.0/safeai/iac/k8s.py +216 -0
  9. safeai_static_analyzer-2.5.0/safeai/iac/linking.py +224 -0
  10. safeai_static_analyzer-2.5.0/safeai/iac/model.py +148 -0
  11. safeai_static_analyzer-2.5.0/safeai/iac/semantics.py +198 -0
  12. safeai_static_analyzer-2.5.0/safeai/iac/terraform.py +524 -0
  13. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/assurance.py +34 -0
  14. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/contract.py +40 -1
  15. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/enrich.py +7 -3
  16. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/manifest.py +33 -0
  17. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/pr_comment.py +118 -12
  18. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/terminal.py +38 -17
  19. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/base_rules.yaml +13 -0
  20. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/version.py +1 -1
  21. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_architectural_invariants.py +85 -1
  22. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_evidence_contract_v7.py +5 -2
  23. safeai_static_analyzer-2.5.0/tests/test_iac_adversarial.py +181 -0
  24. safeai_static_analyzer-2.5.0/tests/test_iac_benchmark.py +201 -0
  25. safeai_static_analyzer-2.5.0/tests/test_iac_correlation.py +270 -0
  26. safeai_static_analyzer-2.5.0/tests/test_iac_k8s.py +153 -0
  27. safeai_static_analyzer-2.5.0/tests/test_iac_linking.py +100 -0
  28. safeai_static_analyzer-2.5.0/tests/test_iac_semantics.py +93 -0
  29. safeai_static_analyzer-2.5.0/tests/test_iac_terraform.py +252 -0
  30. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_manifest_contract.py +122 -0
  31. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_comment.py +187 -0
  32. safeai_static_analyzer-2.5.0/tests/test_release_workflow.py +331 -0
  33. safeai_static_analyzer-2.5.0/tests/test_terminal_report.py +116 -0
  34. safeai_static_analyzer-2.4.0/tests/test_release_workflow.py +0 -114
  35. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/LICENSE +0 -0
  36. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/dependency_links.txt +0 -0
  37. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/entry_points.txt +0 -0
  38. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/requires.txt +0 -0
  39. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/top_level.txt +0 -0
  40. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/pyproject.toml +0 -0
  41. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/__init__.py +0 -0
  42. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/__main__.py +0 -0
  43. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/__init__.py +0 -0
  44. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/aggregation.py +0 -0
  45. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/capabilities.py +0 -0
  46. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/capability_diff.py +0 -0
  47. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/component_diff.py +0 -0
  48. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/component_graph.py +0 -0
  49. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/components.py +0 -0
  50. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/dependency_correlation.py +0 -0
  51. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/escalation.py +0 -0
  52. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/escalation_remediation.py +0 -0
  53. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/import_graph.py +0 -0
  54. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/project_graph.py +0 -0
  55. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/semantic.py +0 -0
  56. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/target_taxonomy.py +0 -0
  57. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_identity.py +0 -0
  58. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_implementation.py +0 -0
  59. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_surface.py +0 -0
  60. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/__init__.py +0 -0
  61. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/capability/__init__.py +0 -0
  62. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/capability/analyzer.py +0 -0
  63. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/claude_code/__init__.py +0 -0
  64. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/claude_code/analyzer.py +0 -0
  65. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/data_leakage/__init__.py +0 -0
  66. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/data_leakage/analyzer.py +0 -0
  67. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/dataflow/__init__.py +0 -0
  68. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/dataflow/analyzer.py +0 -0
  69. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/env_dependency/__init__.py +0 -0
  70. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/env_dependency/analyzer.py +0 -0
  71. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/governance/__init__.py +0 -0
  72. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/governance/analyzer.py +0 -0
  73. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/__init__.py +0 -0
  74. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/analyzer.py +0 -0
  75. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/compatibility.py +0 -0
  76. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/schema.py +0 -0
  77. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/validators.py +0 -0
  78. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/model_config/__init__.py +0 -0
  79. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/model_config/analyzer.py +0 -0
  80. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt/__init__.py +0 -0
  81. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt/analyzer.py +0 -0
  82. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt_file/__init__.py +0 -0
  83. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt_file/analyzer.py +0 -0
  84. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/skill/__init__.py +0 -0
  85. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/skill/analyzer.py +0 -0
  86. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/tool_def/__init__.py +0 -0
  87. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/tool_def/analyzer.py +0 -0
  88. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/workflow/__init__.py +0 -0
  89. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/workflow/analyzer.py +0 -0
  90. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/__init__.py +0 -0
  91. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/cli.py +0 -0
  92. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/manifest_cli.py +0 -0
  93. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/postprocess.py +0 -0
  94. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/registry_cli.py +0 -0
  95. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/__init__.py +0 -0
  96. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/catalogs.py +0 -0
  97. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/mappings.py +0 -0
  98. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/__init__.py +0 -0
  99. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/file_discovery.py +0 -0
  100. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/metadata.py +0 -0
  101. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/scan.py +0 -0
  102. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/__init__.py +0 -0
  103. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/autogen/parser.py +0 -0
  104. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/azure_foundry/__init__.py +0 -0
  105. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/azure_foundry/parser.py +0 -0
  106. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/bedrock_agent/__init__.py +0 -0
  107. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/bedrock_agent/parser.py +0 -0
  108. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/__init__.py +0 -0
  109. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/commands.py +0 -0
  110. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/parser.py +0 -0
  111. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/permissions.py +0 -0
  112. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/settings.py +0 -0
  113. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/copilot/__init__.py +0 -0
  114. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/copilot/parser.py +0 -0
  115. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/crewai/__init__.py +0 -0
  116. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/crewai/parser.py +0 -0
  117. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/cursorrules/__init__.py +0 -0
  118. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/cursorrules/parser.py +0 -0
  119. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/dify/__init__.py +0 -0
  120. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/dify/parser.py +0 -0
  121. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/google_adk/__init__.py +0 -0
  122. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/google_adk/parser.py +0 -0
  123. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/haystack/__init__.py +0 -0
  124. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/haystack/parser.py +0 -0
  125. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langchain/__init__.py +0 -0
  126. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langchain/parser.py +0 -0
  127. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langgraph/__init__.py +0 -0
  128. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langgraph/parser.py +0 -0
  129. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/llamaindex/__init__.py +0 -0
  130. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/llamaindex/parser.py +0 -0
  131. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/mastra/__init__.py +0 -0
  132. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/mastra/parser.py +0 -0
  133. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/microsoft_agent/__init__.py +0 -0
  134. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/microsoft_agent/parser.py +0 -0
  135. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/n8n/__init__.py +0 -0
  136. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/n8n/parser.py +0 -0
  137. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openai_agents/__init__.py +0 -0
  138. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openai_agents/parser.py +0 -0
  139. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openclaw/__init__.py +0 -0
  140. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openclaw/parser.py +0 -0
  141. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/semantic_kernel/__init__.py +0 -0
  142. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/semantic_kernel/parser.py +0 -0
  143. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/windsurf/__init__.py +0 -0
  144. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/windsurf/parser.py +0 -0
  145. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/__init__.py +0 -0
  146. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/baseline.py +0 -0
  147. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/ci_context.py +0 -0
  148. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/exceptions.py +0 -0
  149. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/exporter.py +0 -0
  150. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/fingerprints.py +0 -0
  151. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/identity.py +0 -0
  152. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/importer.py +0 -0
  153. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/integrity.py +0 -0
  154. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/lockfile.py +0 -0
  155. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/policy.py +0 -0
  156. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/__init__.py +0 -0
  157. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/connection.py +0 -0
  158. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/persist.py +0 -0
  159. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/queries.py +0 -0
  160. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/schema.py +0 -0
  161. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/suppressions.py +0 -0
  162. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/util.py +0 -0
  163. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/__init__.py +0 -0
  164. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/failure_matrix.py +0 -0
  165. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/html.py +0 -0
  166. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/html_kit.py +0 -0
  167. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/json_report.py +0 -0
  168. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/registry_html.py +0 -0
  169. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/sarif.py +0 -0
  170. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/__init__.py +0 -0
  171. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/loader.py +0 -0
  172. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/pack_test.py +0 -0
  173. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scorecard.py +0 -0
  174. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scoring/__init__.py +0 -0
  175. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scoring/engine.py +0 -0
  176. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/severity.py +0 -0
  177. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/__init__.py +0 -0
  178. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/client.py +0 -0
  179. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/config.py +0 -0
  180. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/schema.py +0 -0
  181. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/setup.cfg +0 -0
  182. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_adapter_dataflow.py +0 -0
  183. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_architecture_diagram.py +0 -0
  184. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_assurance.py +0 -0
  185. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_authority_change.py +0 -0
  186. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_baseline.py +0 -0
  187. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_baseline_compat.py +0 -0
  188. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_benchmarks.py +0 -0
  189. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_capability_detection.py +0 -0
  190. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_capability_diff_v2.py +0 -0
  191. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_ci_context.py +0 -0
  192. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_claude_code_deep.py +0 -0
  193. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_compatibility.py +0 -0
  194. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_component_graph.py +0 -0
  195. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_component_hash.py +0 -0
  196. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_confidence_arbitration.py +0 -0
  197. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_config_adapters.py +0 -0
  198. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_control_mappings.py +0 -0
  199. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_crewai_framework.py +0 -0
  200. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_cursorrules_framework.py +0 -0
  201. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_dco.py +0 -0
  202. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_env_dependency.py +0 -0
  203. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_escalation.py +0 -0
  204. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_escalation_remediation.py +0 -0
  205. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_evidence_type.py +0 -0
  206. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_exceptions.py +0 -0
  207. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_fingerprints.py +0 -0
  208. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_github_action.py +0 -0
  209. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_governance_analyzer.py +0 -0
  210. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_html_report.py +0 -0
  211. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_init_and_rules.py +0 -0
  212. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_kya_integration.py +0 -0
  213. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_kya_manifest.py +0 -0
  214. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_langgraph_framework.py +0 -0
  215. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_llamaindex_framework.py +0 -0
  216. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_manifest_integrity.py +0 -0
  217. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_mcp_tool_poisoning.py +0 -0
  218. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_mcp_validation.py +0 -0
  219. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_n8n_workflow.py +0 -0
  220. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_parser_aggregation.py +0 -0
  221. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_phase15.py +0 -0
  222. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_plugin_sdk.py +0 -0
  223. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_policy.py +0 -0
  224. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_comment_cli.py +0 -0
  225. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_paths.py +0 -0
  226. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_quality_gates.py +0 -0
  227. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry.py +0 -0
  228. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry_cli.py +0 -0
  229. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry_html.py +0 -0
  230. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_regression_fixtures.py +0 -0
  231. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_assets.py +0 -0
  232. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_fixes.py +0 -0
  233. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_validation.py +0 -0
  234. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_rules_loader_determinism.py +0 -0
  235. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_sarif.py +0 -0
  236. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scan_framework.py +0 -0
  237. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scan_phase11.py +0 -0
  238. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scorecard.py +0 -0
  239. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scoring.py +0 -0
  240. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_semantic.py +0 -0
  241. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_severity.py +0 -0
  242. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_suppressions.py +0 -0
  243. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_target_taxonomy_engine.py +0 -0
  244. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_telemetry.py +0 -0
  245. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_tool_identity.py +0 -0
  246. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_tool_implementation.py +0 -0
  247. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_unknown_authority.py +0 -0
  248. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_version.py +0 -0
  249. {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_windsurf_framework.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SafeAI-Static-Analyzer
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: The lightweight, open-source Static AI Capability & Risk Analyzer for CI/CD pipelines.
5
5
  Author: IkarusCareer
6
6
  License-Expression: Apache-2.0
@@ -36,7 +36,7 @@ Dynamic: license-file
36
36
  [![CI](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml/badge.svg)](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
37
37
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ikaruscareer/SafeAI/badge)](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
38
38
  [![Website](https://img.shields.io/badge/web-safeai--analyzer.ikaruscareer.com-0f766e)](https://safeai-analyzer.ikaruscareer.com)
39
- [![Latest Release](https://img.shields.io/badge/latest-v2.3.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.3.0)
39
+ [![Latest Release](https://img.shields.io/badge/latest-v2.4.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
40
40
  [![Best Practices](https://bestpractices.dev/projects/14126/badge)](https://www.bestpractices.dev/en/projects/14126)
41
41
 
42
42
  Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
@@ -603,6 +603,11 @@ contributors who have helped make AI safer:
603
603
  | [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
604
604
  | [@Teachmeplaycode](https://github.com/Teachmeplaycode) | Benchmark corpus expansion, static subprocess precision gap documentation (PR #174) |
605
605
  | [@nikitajos7](https://github.com/nikitajos7) | Policy profile display in terminal and HTML reports (PR #175) |
606
+ | [@be-student](https://github.com/be-student) | Portable registry import, MCP tool-pattern golden fixtures, canonical release checklist (PRs #123, #124, #125) |
607
+ | [@Karthik9849309055](https://github.com/Karthik9849309055) | Multi-framework scan automation script (PR #161) |
608
+ | [@chenzeyan54-commits](https://github.com/chenzeyan54-commits) | Terminal output sanitization against ANSI/OSC injection (PR #203) |
609
+ | [@HarshRajSinghania](https://github.com/HarshRajSinghania) | PR comment markdown injection sanitization (PR #204) |
610
+ | [@thadidaniel-ctrl](https://github.com/thadidaniel-ctrl) | Release workflow supply-chain posture tests (PR #206) |
606
611
 
607
612
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
608
613
 
@@ -3,7 +3,7 @@
3
3
  [![CI](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml/badge.svg)](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
4
4
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ikaruscareer/SafeAI/badge)](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
5
5
  [![Website](https://img.shields.io/badge/web-safeai--analyzer.ikaruscareer.com-0f766e)](https://safeai-analyzer.ikaruscareer.com)
6
- [![Latest Release](https://img.shields.io/badge/latest-v2.3.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.3.0)
6
+ [![Latest Release](https://img.shields.io/badge/latest-v2.4.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
7
7
  [![Best Practices](https://bestpractices.dev/projects/14126/badge)](https://www.bestpractices.dev/en/projects/14126)
8
8
 
9
9
  Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
@@ -570,6 +570,11 @@ contributors who have helped make AI safer:
570
570
  | [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
571
571
  | [@Teachmeplaycode](https://github.com/Teachmeplaycode) | Benchmark corpus expansion, static subprocess precision gap documentation (PR #174) |
572
572
  | [@nikitajos7](https://github.com/nikitajos7) | Policy profile display in terminal and HTML reports (PR #175) |
573
+ | [@be-student](https://github.com/be-student) | Portable registry import, MCP tool-pattern golden fixtures, canonical release checklist (PRs #123, #124, #125) |
574
+ | [@Karthik9849309055](https://github.com/Karthik9849309055) | Multi-framework scan automation script (PR #161) |
575
+ | [@chenzeyan54-commits](https://github.com/chenzeyan54-commits) | Terminal output sanitization against ANSI/OSC injection (PR #203) |
576
+ | [@HarshRajSinghania](https://github.com/HarshRajSinghania) | PR comment markdown injection sanitization (PR #204) |
577
+ | [@thadidaniel-ctrl](https://github.com/thadidaniel-ctrl) | Release workflow supply-chain posture tests (PR #206) |
573
578
 
574
579
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
575
580
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SafeAI-Static-Analyzer
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: The lightweight, open-source Static AI Capability & Risk Analyzer for CI/CD pipelines.
5
5
  Author: IkarusCareer
6
6
  License-Expression: Apache-2.0
@@ -36,7 +36,7 @@ Dynamic: license-file
36
36
  [![CI](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml/badge.svg)](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
37
37
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ikaruscareer/SafeAI/badge)](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
38
38
  [![Website](https://img.shields.io/badge/web-safeai--analyzer.ikaruscareer.com-0f766e)](https://safeai-analyzer.ikaruscareer.com)
39
- [![Latest Release](https://img.shields.io/badge/latest-v2.3.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.3.0)
39
+ [![Latest Release](https://img.shields.io/badge/latest-v2.4.0-0f766e)](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
40
40
  [![Best Practices](https://bestpractices.dev/projects/14126/badge)](https://www.bestpractices.dev/en/projects/14126)
41
41
 
42
42
  Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
@@ -603,6 +603,11 @@ contributors who have helped make AI safer:
603
603
  | [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
604
604
  | [@Teachmeplaycode](https://github.com/Teachmeplaycode) | Benchmark corpus expansion, static subprocess precision gap documentation (PR #174) |
605
605
  | [@nikitajos7](https://github.com/nikitajos7) | Policy profile display in terminal and HTML reports (PR #175) |
606
+ | [@be-student](https://github.com/be-student) | Portable registry import, MCP tool-pattern golden fixtures, canonical release checklist (PRs #123, #124, #125) |
607
+ | [@Karthik9849309055](https://github.com/Karthik9849309055) | Multi-framework scan automation script (PR #161) |
608
+ | [@chenzeyan54-commits](https://github.com/chenzeyan54-commits) | Terminal output sanitization against ANSI/OSC injection (PR #203) |
609
+ | [@HarshRajSinghania](https://github.com/HarshRajSinghania) | PR comment markdown injection sanitization (PR #204) |
610
+ | [@thadidaniel-ctrl](https://github.com/thadidaniel-ctrl) | Release workflow supply-chain posture tests (PR #206) |
606
611
 
607
612
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
608
613
 
@@ -22,6 +22,7 @@ safeai/analysis/components.py
22
22
  safeai/analysis/dependency_correlation.py
23
23
  safeai/analysis/escalation.py
24
24
  safeai/analysis/escalation_remediation.py
25
+ safeai/analysis/iac_correlation.py
25
26
  safeai/analysis/import_graph.py
26
27
  safeai/analysis/project_graph.py
27
28
  safeai/analysis/semantic.py
@@ -115,6 +116,12 @@ safeai/frameworks/semantic_kernel/__init__.py
115
116
  safeai/frameworks/semantic_kernel/parser.py
116
117
  safeai/frameworks/windsurf/__init__.py
117
118
  safeai/frameworks/windsurf/parser.py
119
+ safeai/iac/__init__.py
120
+ safeai/iac/k8s.py
121
+ safeai/iac/linking.py
122
+ safeai/iac/model.py
123
+ safeai/iac/semantics.py
124
+ safeai/iac/terraform.py
118
125
  safeai/kya/__init__.py
119
126
  safeai/kya/assurance.py
120
127
  safeai/kya/baseline.py
@@ -187,6 +194,13 @@ tests/test_fingerprints.py
187
194
  tests/test_github_action.py
188
195
  tests/test_governance_analyzer.py
189
196
  tests/test_html_report.py
197
+ tests/test_iac_adversarial.py
198
+ tests/test_iac_benchmark.py
199
+ tests/test_iac_correlation.py
200
+ tests/test_iac_k8s.py
201
+ tests/test_iac_linking.py
202
+ tests/test_iac_semantics.py
203
+ tests/test_iac_terraform.py
190
204
  tests/test_init_and_rules.py
191
205
  tests/test_kya_integration.py
192
206
  tests/test_kya_manifest.py
@@ -224,6 +238,7 @@ tests/test_severity.py
224
238
  tests/test_suppressions.py
225
239
  tests/test_target_taxonomy_engine.py
226
240
  tests/test_telemetry.py
241
+ tests/test_terminal_report.py
227
242
  tests/test_tool_identity.py
228
243
  tests/test_tool_implementation.py
229
244
  tests/test_unknown_authority.py
@@ -0,0 +1,482 @@
1
+ """IaC authority correlation engine (v2.5 redesign).
2
+
3
+ Operates on the explicit authority graph — Identities, Grants,
4
+ GrantBindings, AgentIdentityLinks — never on bare category families.
5
+ Verdict semantics (strict):
6
+
7
+ - ``MATCH`` — Agent→Identity link statically evidenced, declared
8
+ requirements map to the grant domain, a compatible grant exists, and
9
+ no unresolved material could change the conclusion.
10
+ - ``EXCESS_AUTHORITY`` — linked grants strictly exceed what the linked
11
+ agent's requirements need. Unrelated grants are never excess.
12
+ - ``AUTHORITY_MISMATCH`` — linked identity visibly lacks a grant the
13
+ requirements need, with authoritative (fully resolved, module-free)
14
+ evidence. Absence alone is never mismatch.
15
+ - ``UNVERIFIED_LINK`` — requirements and grants coexist without a
16
+ static link. The most common outcome for ambiguous projects.
17
+ - ``UNKNOWN`` — unresolved material, modules, unparsed IaC, or no
18
+ assessable counterpart. Preferred over false mismatch/excess.
19
+
20
+ Every finding pre-sets ``provenance_class="repo-iac-observed"`` and
21
+ ``gateability="review-only"`` (Lane B, ADR-0008). Repository IaC is
22
+ evidence of declared grants, never proof of deployed permission.
23
+ """
24
+
25
+ from safeai.iac import semantics
26
+ from safeai.iac.model import (
27
+ PARTIALLY_RESOLVED,
28
+ RESOLVED,
29
+ UNRESOLVED,
30
+ verdict,
31
+ )
32
+
33
+ #: Correlation finding rule ids (stable public vocabulary).
34
+ RULE_EXCESS = "IAC_EXCESS_AUTHORITY"
35
+ RULE_MISMATCH = "IAC_AUTHORITY_MISMATCH"
36
+ RULE_UNVERIFIED = "IAC_UNVERIFIED_LINK"
37
+
38
+ _PROVENANCE = "repo-iac-observed"
39
+ _GATEABILITY = "review-only"
40
+
41
+
42
+ def _surface_requirements(report):
43
+ """Build semantic requirements from the tool surface.
44
+
45
+ Returns ``[(agent_ref, descriptor, evidence_refs)]``. Capabilities
46
+ without an IaC-mappable domain, or without an access mode, yield
47
+ ``descriptor None`` (uncomparable — forces UNKNOWN when linked,
48
+ ignored otherwise).
49
+ """
50
+ requirements = []
51
+ surface = report.get("tool_surface")
52
+ tools = (surface if isinstance(surface, list)
53
+ else (surface or {}).get("tools") or [])
54
+ for entry in tools:
55
+ if not isinstance(entry, dict):
56
+ continue
57
+ agent_ref = str(entry.get("tool_key") or "<unknown-tool>")
58
+ by_domain = {}
59
+ evidence = {}
60
+ for cap in entry.get("capabilities") or []:
61
+ if not isinstance(cap, dict):
62
+ continue
63
+ descriptor = semantics.normalize_requirement(
64
+ cap.get("name"), cap.get("access_mode"))
65
+ if descriptor is None:
66
+ continue
67
+ domain = (descriptor["provider"], descriptor["service"])
68
+ by_domain.setdefault(domain, set()).update(descriptor["ops"])
69
+ for item in cap.get("evidence") or []:
70
+ if isinstance(item, dict) and item.get("path"):
71
+ evidence.setdefault(domain, set()).add(
72
+ f"{item.get('path')}:{item.get('line', 0)}")
73
+ for domain, ops in by_domain.items():
74
+ requirements.append({
75
+ "agent_ref": agent_ref,
76
+ "provider": domain[0],
77
+ "service": domain[1],
78
+ "ops": set(ops),
79
+ "evidence_refs": sorted(evidence.get(domain, ())),
80
+ })
81
+ return requirements
82
+
83
+
84
+ def _grant_domains(grants):
85
+ """Map (provider, service) -> list of grants with descriptors."""
86
+ domains = {}
87
+ for grant in grants or []:
88
+ if not isinstance(grant, dict):
89
+ continue
90
+ descriptor, unresolvable = semantics.grant_descriptor(grant)
91
+ domain = (descriptor["provider"], descriptor["service"])
92
+ domains.setdefault(domain, []).append((grant, descriptor,
93
+ unresolvable))
94
+ return domains
95
+
96
+
97
+ def _identity_links_map(links):
98
+ """Map identity key -> list of links."""
99
+ mapped = {}
100
+ for link in links or []:
101
+ if not isinstance(link, dict):
102
+ continue
103
+ ident = link.get("identity") or {}
104
+ key = (str(ident.get("kind") or ""),
105
+ str(ident.get("namespace") or ""),
106
+ str(ident.get("name") or ""))
107
+ mapped.setdefault(key, []).append(link)
108
+ return mapped
109
+
110
+
111
+ def link_refs_for(keys, links_by_identity):
112
+ """Evidence refs for a set of linked identity keys (deterministic)."""
113
+ refs = set()
114
+ for key in keys or []:
115
+ for link in links_by_identity.get(key) or []:
116
+ for ev in link.get("evidence") or []:
117
+ refs.add(f"{ev.get('file')}:{ev.get('line', 0)}")
118
+ return sorted(refs)
119
+
120
+
121
+ def _expand_admin_grants(domains, req_domains):
122
+ """Share bare-star grants across observed AWS domains.
123
+
124
+ A literal ``*`` action is provider-wide admin: it genuinely covers
125
+ requirements in any domain. Evaluating it only in its own ``(*,*)``
126
+ bucket would hide privilege behind the wildcard (and fabricate
127
+ MISMATCH next to real coverage). The ``(*,*)`` record itself is
128
+ kept as UNKNOWN inventory; copies participate per-domain. Scoped
129
+ to AWS — the only producer of provider-"*" descriptors.
130
+ """
131
+ star_items = []
132
+ for domain, items in domains.items():
133
+ if domain == ("*", "*"):
134
+ star_items.extend(items)
135
+ if not star_items:
136
+ return domains
137
+ expanded = dict(domains)
138
+ targets = ({d for d in req_domains if d[0] == "aws"}
139
+ | {d for d in domains
140
+ if d != ("*", "*") and d[0] == "aws"})
141
+ for domain in sorted(targets):
142
+ expanded.setdefault(domain, []).extend(star_items)
143
+ return expanded
144
+
145
+
146
+ def _shadowed_providers(grants):
147
+ """Providers whose grant set contains unresolved material."""
148
+ """Providers whose grant set contains unresolved material.
149
+
150
+ An unresolved grant could hide permissions in its provider, so
151
+ absence claims (MISMATCH) are degraded to UNKNOWN there. Empty
152
+ grants fall back to the source family hint (cloud→aws); truly
153
+ unknown origins shadow everything.
154
+ """
155
+ shadowed = set()
156
+ for grant in grants or []:
157
+ if not isinstance(grant, dict):
158
+ continue
159
+ actions = grant.get("actions") or {}
160
+ resources = grant.get("resources") or {}
161
+ if actions.get("resolution") == UNRESOLVED or \
162
+ resources.get("resolution") == UNRESOLVED:
163
+ descriptor, _ = semantics.grant_descriptor(grant)
164
+ provider = descriptor.get("provider") or "*"
165
+ if provider == "*":
166
+ provider = {"cloud": "aws",
167
+ "kubernetes": "kubernetes"}.get(
168
+ grant.get("family") or "", "*")
169
+ shadowed.add(provider)
170
+ return shadowed
171
+
172
+
173
+ def _grant_identity_key(grant):
174
+ """Full identity triple for a grant (namespaces isolate)."""
175
+ """Full identity triple for a grant (namespaces isolate)."""
176
+ ident = grant.get("identity") or {}
177
+ return (str(ident.get("kind") or ""), str(ident.get("namespace") or ""),
178
+ str(ident.get("name") or ""))
179
+
180
+
181
+ def correlate_iac_authority(report, graph):
182
+ """Correlate declared requirements against evidenced grants.
183
+
184
+ ``graph`` holds ``identities``, ``grants``, ``grant_bindings``,
185
+ ``agent_links``, and ``meta``. Returns ``(findings, summary)`` with
186
+ the v2 ``iac_correlations`` shape. Never raises on malformed input.
187
+ """
188
+ graph = graph or {}
189
+ grants = [g for g in graph.get("grants") or [] if isinstance(g, dict)]
190
+ links = [l for l in graph.get("agent_links") or []
191
+ if isinstance(l, dict)]
192
+ meta = graph.get("meta") or {}
193
+
194
+ requirements = _surface_requirements(report)
195
+ domains = _grant_domains(grants)
196
+ links_by_identity = _identity_links_map(links)
197
+ agent_linked = sorted(links_by_identity)
198
+ shadowed = _shadowed_providers(grants)
199
+
200
+ req_domains = {}
201
+ for req in requirements:
202
+ req_domains.setdefault((req["provider"], req["service"]), []).append(req)
203
+ # A requirement for "any service" ((aws, *)) applies to every
204
+ # observed service of that provider.
205
+ expanded = {}
206
+ for domain, reqs in req_domains.items():
207
+ provider, service = domain
208
+ if service == "*":
209
+ targets = [o for o in domains
210
+ if o[0] == provider and o != domain]
211
+ if targets:
212
+ # A wildcard-service requirement is evaluated against
213
+ # each observed service; the bare (provider, *) self
214
+ # domain would only duplicate those verdicts.
215
+ for observed in targets:
216
+ expanded.setdefault(observed, []).extend(reqs)
217
+ continue
218
+ expanded.setdefault(domain, []).extend(reqs)
219
+ req_domains = expanded
220
+
221
+ # Bare-star grants (provider-wide admin from Terraform) genuinely
222
+ # cover requirements in every AWS domain — including domains with
223
+ # no other grants, where they prevent false MISMATCH. Scoped to
224
+ # AWS: only Terraform produces provider-"*" descriptors today, and
225
+ # a TF star is AWS-admin, never Kubernetes-admin.
226
+ domains = _expand_admin_grants(domains, req_domains)
227
+
228
+ findings = []
229
+ verdicts = []
230
+ modules = bool(meta.get("has_modules"))
231
+ unparsed = bool(meta.get("unparsed_files"))
232
+
233
+ for domain in sorted(set(req_domains) | set(domains)):
234
+ verdict_obj = _decide_domain(
235
+ domain, req_domains.get(domain, []),
236
+ domains.get(domain, []), links_by_identity, agent_linked,
237
+ shadowed,
238
+ authoritative=not (modules or unparsed))
239
+ if verdict_obj is None:
240
+ continue
241
+ verdicts.append(verdict_obj)
242
+ finding = _verdict_finding(verdict_obj)
243
+ if finding is not None:
244
+ findings.append(finding)
245
+
246
+ findings.sort(key=lambda f: (f.get("file", ""), int(f.get("line") or 0),
247
+ f.get("rule_id", "")))
248
+ counts = {"grants": len(grants), "verdicts": len(verdicts),
249
+ "identities": len(graph.get("identities") or []),
250
+ "agent_links": len(links)}
251
+ for item in verdicts:
252
+ key = str(item.get("verdict") or "UNKNOWN").lower()
253
+ counts[key] = counts.get(key, 0) + 1
254
+ files = dict(meta.get("files") or {})
255
+ summary = {
256
+ "schema_version": 2,
257
+ "correlation_model": "authority-graph v2 (identities, grants, "
258
+ "bindings, links; ADR-0007/0009)",
259
+ "lane": "B",
260
+ "files": files,
261
+ "identities": graph.get("identities") or [],
262
+ "agent_identity_links": links,
263
+ "grants": grants,
264
+ "grant_bindings": graph.get("grant_bindings") or [],
265
+ "verdicts": verdicts,
266
+ "counts": counts,
267
+ }
268
+ return findings, summary
269
+
270
+
271
+ def _decide_domain(domain, reqs, grant_items, links_by_identity,
272
+ agent_linked, shadowed, authoritative):
273
+ """Decide one (provider, service) domain. None when nothing to say."""
274
+ provider, service = domain
275
+ domain_name = f"{provider}:{service}"
276
+
277
+ linked_grants = []
278
+ linked_identities = []
279
+ for grant, descriptor, unresolvable in grant_items:
280
+ # Exact triple match (kind, namespace, name): namespaces
281
+ # isolate — a same-named identity in another namespace never
282
+ # links. AWS identities carry no namespace on either side.
283
+ key = _grant_identity_key(grant)
284
+ if key not in links_by_identity:
285
+ continue
286
+ link = links_by_identity[key][0]
287
+ linked_grants.append((grant, descriptor, unresolvable, link, key))
288
+ if key not in linked_identities:
289
+ linked_identities.append(key)
290
+
291
+ declared_refs = sorted({ref for req in reqs
292
+ for ref in req.get("evidence_refs") or []}
293
+ | {req.get("agent_ref") for req in reqs})
294
+ grant_refs = sorted({f"{g.get('source_file')}:{g.get('line', 0)}"
295
+ for g, _, _ in grant_items})
296
+ link_refs = sorted({ref
297
+ for _, _, _, link, _ in linked_grants
298
+ for ev in (link.get("evidence") or [])
299
+ for ref in [f"{ev.get('file')}:{ev.get('line', 0)}"]})
300
+
301
+ required_ops = set()
302
+ for req in reqs:
303
+ required_ops |= set(req.get("ops") or [])
304
+
305
+ if reqs and linked_grants:
306
+ return _decide_linked(domain, reqs, required_ops,
307
+ linked_grants, declared_refs, grant_refs,
308
+ link_refs, shadowed,
309
+ authoritative=authoritative)
310
+ if reqs and grant_items:
311
+ return verdict("<repo>", None, domain_name, "UNVERIFIED_LINK",
312
+ declared_refs, grant_refs, [],
313
+ PARTIALLY_RESOLVED
314
+ if any(u for _, _, u in grant_items) else RESOLVED,
315
+ "Declared requirements and IaC grants coexist in "
316
+ f"{domain_name} without a static Agent-to-Identity "
317
+ "link; the grant may or may not reach this agent.")
318
+ if reqs and not grant_items:
319
+ # No grant in this domain. MISMATCH only when the agent is
320
+ # linked, the evidence is authoritative, and no unresolved
321
+ # grant in this provider could hide the permission.
322
+ # Otherwise UNKNOWN — absence alone is never mismatch.
323
+ shadowed_here = (provider in shadowed or "*" in shadowed)
324
+ if agent_linked and authoritative and not shadowed_here:
325
+ names = ", ".join(sorted({k[2] for k in agent_linked}))
326
+ return verdict(
327
+ "<repo>", None, domain_name, "AUTHORITY_MISMATCH",
328
+ declared_refs, [], link_refs_for(agent_linked,
329
+ links_by_identity),
330
+ RESOLVED,
331
+ f"Linked identit{'y' if len(agent_linked) == 1 else 'ies'} "
332
+ f"{names} show no {domain_name} grant in authoritative "
333
+ "repository evidence — probable breakage, or the grant "
334
+ "lives outside this repository.")
335
+ if agent_linked:
336
+ return verdict("<repo>", None, domain_name, "UNKNOWN",
337
+ declared_refs, [],
338
+ link_refs_for(agent_linked, links_by_identity),
339
+ UNRESOLVED,
340
+ "Requirements exist but unresolved material "
341
+ "prevents showing an authoritative absence.")
342
+ return verdict("<repo>", None, domain_name, "UNKNOWN",
343
+ declared_refs, [], [],
344
+ PARTIALLY_RESOLVED,
345
+ "Requirements exist but no comparable grant is "
346
+ "visible and no Agent-to-Identity link establishes "
347
+ "where to look.")
348
+ if grant_items and not reqs:
349
+ material_unresolved = any(u for _, _, u in grant_items)
350
+ return verdict("<repo>", None, domain_name, "UNKNOWN",
351
+ [], grant_refs, [],
352
+ UNRESOLVED if material_unresolved else RESOLVED,
353
+ f"IaC grants {domain_name} authority with no declared "
354
+ "capability counterpart — recorded, not excess: "
355
+ "without a linked requirement there is insufficient "
356
+ "evidence of over-privilege.")
357
+ return None
358
+
359
+
360
+ def _decide_linked(domain, reqs, required_ops, linked_grants,
361
+ declared_refs, grant_refs, link_refs, shadowed,
362
+ authoritative):
363
+ """Decide a domain with linked identity grants (strict semantics)."""
364
+ provider, service = domain
365
+ domain_name = f"{provider}:{service}"
366
+ material_unresolved = any(u for _, _, u, _, _ in linked_grants)
367
+ high_link = any(str(link.get("confidence") or "").lower() == "high"
368
+ for _, _, _, link, _ in linked_grants)
369
+ identity_ref = {"kind": linked_grants[0][4][0],
370
+ "namespace": linked_grants[0][4][1],
371
+ "name": linked_grants[0][4][2]}
372
+
373
+ if material_unresolved or not authoritative:
374
+ return verdict("<repo>", identity_ref, domain_name, "UNKNOWN",
375
+ declared_refs, grant_refs, link_refs, UNRESOLVED,
376
+ "Unresolved grant content, unparsed IaC, or modules "
377
+ "could change the conclusion — UNKNOWN preferred "
378
+ "over a false match/mismatch.")
379
+
380
+ covered, excess_union, notes = False, set(), []
381
+ req_like = {"provider": provider, "service": service,
382
+ "ops": required_ops}
383
+ for grant, descriptor, _u, _link, _key in linked_grants:
384
+ grant_cov, grant_excess, grant_notes = semantics.compatible(
385
+ descriptor, req_like)
386
+ notes.extend(grant_notes)
387
+ covered = covered or grant_cov
388
+ excess_union |= grant_excess
389
+
390
+ resolution = RESOLVED if high_link else PARTIALLY_RESOLVED
391
+ if covered and not excess_union:
392
+ return verdict("<repo>", identity_ref, domain_name, "MATCH",
393
+ declared_refs, grant_refs, link_refs, resolution,
394
+ f"Linked grant covers {domain_name} requirements "
395
+ f"({', '.join(sorted(required_ops)) or 'none'}) with "
396
+ "no excess.")
397
+ if covered and excess_union:
398
+ return verdict("<repo>", identity_ref, domain_name,
399
+ "EXCESS_AUTHORITY", declared_refs, grant_refs,
400
+ link_refs, resolution,
401
+ f"Linked grant provides {domain_name} operations "
402
+ f"({', '.join(sorted(excess_union))}) beyond what "
403
+ "the declared requirements need.")
404
+ if provider in shadowed or "*" in shadowed:
405
+ return verdict("<repo>", identity_ref, domain_name, "UNKNOWN",
406
+ declared_refs, grant_refs, link_refs, UNRESOLVED,
407
+ "Unresolved grants in this provider could hide the "
408
+ "required permission — UNKNOWN preferred over a "
409
+ "false mismatch.")
410
+ return verdict("<repo>", identity_ref, domain_name,
411
+ "AUTHORITY_MISMATCH", declared_refs, grant_refs,
412
+ link_refs, resolution,
413
+ f"Linked identity shows no compatible {domain_name} "
414
+ "grant for the declared requirements.")
415
+
416
+
417
+ def _verdict_finding(verdict_obj):
418
+ """Build the review-only finding for a verdict (None if silent)."""
419
+ name = verdict_obj.get("verdict")
420
+ if name in ("MATCH", "UNKNOWN"):
421
+ return None
422
+ refs = verdict_obj.get("grant_evidence_refs") or []
423
+ link_refs = verdict_obj.get("link_evidence_refs") or []
424
+ first = (refs + link_refs + ["<scan>:1"])[0]
425
+ location = first.rsplit(":", 1)
426
+ reason = verdict_obj.get("reason") or name
427
+ base = {
428
+ "file": location[0] if len(location) == 2 else "<scan>",
429
+ "line": int(location[1]) if len(location) == 2 and
430
+ str(location[1]).isdigit() else 1,
431
+ "risk_category": "Integration",
432
+ "affected_framework": "generic",
433
+ "affected_capability": verdict_obj.get("domain"),
434
+ "provenance_class": _PROVENANCE,
435
+ "gateability": _GATEABILITY,
436
+ "iac_domain": verdict_obj.get("domain"),
437
+ "iac_verdict": name,
438
+ "evidence": (f"verdict={name} domain={verdict_obj.get('domain')} "
439
+ f"refs={', '.join(refs) or 'none'}"),
440
+ }
441
+ if name == "EXCESS_AUTHORITY":
442
+ base.update({
443
+ "rule_id": RULE_EXCESS,
444
+ "severity": "medium",
445
+ "message": "Linked IaC grant exceeds the declared requirement",
446
+ "owasp_llm": "LLM06",
447
+ "reason": reason,
448
+ "remediation": (
449
+ "Narrow the grant to the required operations, or declare "
450
+ "and justify the wider capability; re-review on IaC change."
451
+ ),
452
+ "confidence": 0.65,
453
+ "score_contribution": 6,
454
+ })
455
+ elif name == "AUTHORITY_MISMATCH":
456
+ base.update({
457
+ "rule_id": RULE_MISMATCH,
458
+ "severity": "low",
459
+ "message": "Linked identity lacks a grant the agent requires",
460
+ "reason": reason,
461
+ "remediation": (
462
+ "Add the missing grant, or confirm it is granted outside "
463
+ "this repository and record that link explicitly."
464
+ ),
465
+ "confidence": 0.55,
466
+ "score_contribution": 3,
467
+ })
468
+ else: # UNVERIFIED_LINK
469
+ base.update({
470
+ "rule_id": RULE_UNVERIFIED,
471
+ "severity": "low",
472
+ "message": "Declared requirement and IaC grant lack a static link",
473
+ "reason": reason,
474
+ "remediation": (
475
+ "Establish which identity the agent assumes (workload "
476
+ "service account, role reference) and record it; without "
477
+ "that link this stays review-only."
478
+ ),
479
+ "confidence": 0.55,
480
+ "score_contribution": 3,
481
+ })
482
+ return base