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.
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/PKG-INFO +7 -2
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/README.md +6 -1
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/PKG-INFO +7 -2
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/SOURCES.txt +15 -0
- safeai_static_analyzer-2.5.0/safeai/analysis/iac_correlation.py +482 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/orchestrator.py +42 -0
- safeai_static_analyzer-2.5.0/safeai/iac/__init__.py +169 -0
- safeai_static_analyzer-2.5.0/safeai/iac/k8s.py +216 -0
- safeai_static_analyzer-2.5.0/safeai/iac/linking.py +224 -0
- safeai_static_analyzer-2.5.0/safeai/iac/model.py +148 -0
- safeai_static_analyzer-2.5.0/safeai/iac/semantics.py +198 -0
- safeai_static_analyzer-2.5.0/safeai/iac/terraform.py +524 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/assurance.py +34 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/contract.py +40 -1
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/enrich.py +7 -3
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/manifest.py +33 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/pr_comment.py +118 -12
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/terminal.py +38 -17
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/base_rules.yaml +13 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/version.py +1 -1
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_architectural_invariants.py +85 -1
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_evidence_contract_v7.py +5 -2
- safeai_static_analyzer-2.5.0/tests/test_iac_adversarial.py +181 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_benchmark.py +201 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_correlation.py +270 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_k8s.py +153 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_linking.py +100 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_semantics.py +93 -0
- safeai_static_analyzer-2.5.0/tests/test_iac_terraform.py +252 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_manifest_contract.py +122 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_comment.py +187 -0
- safeai_static_analyzer-2.5.0/tests/test_release_workflow.py +331 -0
- safeai_static_analyzer-2.5.0/tests/test_terminal_report.py +116 -0
- safeai_static_analyzer-2.4.0/tests/test_release_workflow.py +0 -114
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/LICENSE +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/dependency_links.txt +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/entry_points.txt +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/requires.txt +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/SafeAI_Static_Analyzer.egg-info/top_level.txt +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/pyproject.toml +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/__main__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/aggregation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/capabilities.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/capability_diff.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/component_diff.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/component_graph.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/components.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/dependency_correlation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/escalation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/escalation_remediation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/import_graph.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/project_graph.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/semantic.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/target_taxonomy.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_identity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_implementation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analysis/tool_surface.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/capability/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/capability/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/claude_code/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/claude_code/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/data_leakage/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/data_leakage/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/dataflow/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/dataflow/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/env_dependency/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/env_dependency/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/governance/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/governance/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/compatibility.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/schema.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/mcp/validators.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/model_config/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/model_config/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt_file/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/prompt_file/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/skill/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/skill/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/tool_def/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/tool_def/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/workflow/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/analyzers/workflow/analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/cli.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/manifest_cli.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/postprocess.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/cmd/registry_cli.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/catalogs.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/controls/mappings.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/file_discovery.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/metadata.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/engine/scan.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/autogen/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/azure_foundry/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/azure_foundry/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/bedrock_agent/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/bedrock_agent/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/commands.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/permissions.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/claude_code/settings.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/copilot/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/copilot/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/crewai/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/crewai/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/cursorrules/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/cursorrules/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/dify/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/dify/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/google_adk/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/google_adk/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/haystack/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/haystack/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langchain/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langchain/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langgraph/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/langgraph/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/llamaindex/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/llamaindex/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/mastra/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/mastra/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/microsoft_agent/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/microsoft_agent/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/n8n/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/n8n/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openai_agents/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openai_agents/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openclaw/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/openclaw/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/semantic_kernel/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/semantic_kernel/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/windsurf/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/frameworks/windsurf/parser.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/baseline.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/ci_context.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/exceptions.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/exporter.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/fingerprints.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/identity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/importer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/integrity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/lockfile.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/policy.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/connection.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/persist.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/queries.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/registry/schema.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/suppressions.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/kya/util.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/failure_matrix.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/html.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/html_kit.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/json_report.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/registry_html.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/report/sarif.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/loader.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/rules/pack_test.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scorecard.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scoring/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/scoring/engine.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/severity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/__init__.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/client.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/config.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/safeai/telemetry/schema.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/setup.cfg +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_adapter_dataflow.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_architecture_diagram.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_assurance.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_authority_change.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_baseline.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_baseline_compat.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_benchmarks.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_capability_detection.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_capability_diff_v2.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_ci_context.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_claude_code_deep.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_compatibility.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_component_graph.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_component_hash.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_confidence_arbitration.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_config_adapters.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_control_mappings.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_crewai_framework.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_cursorrules_framework.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_dco.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_env_dependency.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_escalation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_escalation_remediation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_evidence_type.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_exceptions.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_fingerprints.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_github_action.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_governance_analyzer.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_html_report.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_init_and_rules.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_kya_integration.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_kya_manifest.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_langgraph_framework.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_llamaindex_framework.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_manifest_integrity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_mcp_tool_poisoning.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_mcp_validation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_n8n_workflow.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_parser_aggregation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_phase15.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_plugin_sdk.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_policy.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_comment_cli.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_pr_paths.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_quality_gates.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry_cli.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_registry_html.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_regression_fixtures.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_assets.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_fixes.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_release_validation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_rules_loader_determinism.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_sarif.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scan_framework.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scan_phase11.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scorecard.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_scoring.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_semantic.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_severity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_suppressions.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_target_taxonomy_engine.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_telemetry.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_tool_identity.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_tool_implementation.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_unknown_authority.py +0 -0
- {safeai_static_analyzer-2.4.0 → safeai_static_analyzer-2.5.0}/tests/test_version.py +0 -0
- {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.
|
|
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
|
[](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
|
|
37
37
|
[](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
|
|
38
38
|
[](https://safeai-analyzer.ikaruscareer.com)
|
|
39
|
-
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
|
|
40
40
|
[](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
|
[](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
|
|
4
4
|
[](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
|
|
5
5
|
[](https://safeai-analyzer.ikaruscareer.com)
|
|
6
|
-
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
|
|
7
7
|
[](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.
|
|
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
|
[](https://github.com/ikaruscareer/SafeAI/actions/workflows/ci.yml)
|
|
37
37
|
[](https://scorecard.dev/viewer/?uri=github.com/ikaruscareer/SafeAI)
|
|
38
38
|
[](https://safeai-analyzer.ikaruscareer.com)
|
|
39
|
-
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v2.4.0)
|
|
40
40
|
[](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
|