SafeAI-Static-Analyzer 1.9.1__tar.gz → 2.0.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-1.9.1 → safeai_static_analyzer-2.0.0}/PKG-INFO +23 -8
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/README.md +22 -7
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/PKG-INFO +23 -8
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/SOURCES.txt +10 -1
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/capability/analyzer.py +3 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/claude_code/analyzer.py +31 -10
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/data_leakage/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/dataflow/analyzer.py +155 -6
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/env_dependency/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/governance/analyzer.py +21 -1
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/mcp/analyzer.py +75 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/model_config/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/prompt/analyzer.py +9 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/prompt_file/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/skill/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/tool_def/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/workflow/analyzer.py +1 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/cmd/postprocess.py +4 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/controls/mappings.py +54 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/engine/orchestrator.py +3 -1
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/__init__.py +2 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/claude_code/permissions.py +130 -4
- safeai_static_analyzer-2.0.0/safeai/frameworks/cursorrules/parser.py +156 -0
- safeai_static_analyzer-2.0.0/safeai/frameworks/windsurf/parser.py +143 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/assurance.py +26 -0
- safeai_static_analyzer-2.0.0/safeai/report/failure_matrix.py +76 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/html.py +16 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/sarif.py +1 -0
- safeai_static_analyzer-2.0.0/safeai/rules/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/rules/base_rules.yaml +15 -0
- safeai_static_analyzer-2.0.0/safeai/scoring/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/version.py +1 -1
- safeai_static_analyzer-2.0.0/tests/test_adapter_dataflow.py +378 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_claude_code_deep.py +127 -5
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_control_mappings.py +43 -0
- safeai_static_analyzer-2.0.0/tests/test_cursorrules_framework.py +112 -0
- safeai_static_analyzer-2.0.0/tests/test_evidence_type.py +86 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_governance_analyzer.py +80 -1
- safeai_static_analyzer-2.0.0/tests/test_mcp_tool_poisoning.py +147 -0
- safeai_static_analyzer-2.0.0/tests/test_windsurf_framework.py +112 -0
- safeai_static_analyzer-1.9.1/tests/test_adapter_dataflow.py +0 -183
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/LICENSE +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/dependency_links.txt +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/entry_points.txt +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/requires.txt +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/SafeAI_Static_Analyzer.egg-info/top_level.txt +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/pyproject.toml +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/__main__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/aggregation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/capabilities.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/capability_diff.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/component_diff.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/component_graph.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/components.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/dependency_correlation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/escalation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/import_graph.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/project_graph.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/semantic.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/target_taxonomy.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/tool_identity.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/tool_implementation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analysis/tool_surface.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/capability/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/claude_code/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/data_leakage/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/dataflow/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/env_dependency/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/governance/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/mcp/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/mcp/compatibility.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/mcp/schema.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/mcp/validators.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/model_config/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/prompt/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/prompt_file/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/skill/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/tool_def/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/workflow/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/cmd/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/cmd/cli.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/cmd/registry_cli.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/controls/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/controls/catalogs.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/engine/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/engine/scan.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/autogen/parser.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/azure_foundry/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/azure_foundry/parser.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/bedrock_agent/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/bedrock_agent/parser.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/claude_code/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/claude_code/commands.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/claude_code/parser.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/claude_code/settings.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/crewai/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/crewai/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/dify → safeai_static_analyzer-2.0.0/safeai/frameworks/cursorrules}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/google_adk → safeai_static_analyzer-2.0.0/safeai/frameworks/dify}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/dify/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/haystack → safeai_static_analyzer-2.0.0/safeai/frameworks/google_adk}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/google_adk/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/langchain → safeai_static_analyzer-2.0.0/safeai/frameworks/haystack}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/haystack/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/langgraph → safeai_static_analyzer-2.0.0/safeai/frameworks/langchain}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/langchain/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/llamaindex → safeai_static_analyzer-2.0.0/safeai/frameworks/langgraph}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/langgraph/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/mastra → safeai_static_analyzer-2.0.0/safeai/frameworks/llamaindex}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/llamaindex/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/microsoft_agent → safeai_static_analyzer-2.0.0/safeai/frameworks/mastra}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/mastra/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/n8n → safeai_static_analyzer-2.0.0/safeai/frameworks/microsoft_agent}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/microsoft_agent/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/openai_agents → safeai_static_analyzer-2.0.0/safeai/frameworks/n8n}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/n8n/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/frameworks/semantic_kernel → safeai_static_analyzer-2.0.0/safeai/frameworks/openai_agents}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/openai_agents/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/report → safeai_static_analyzer-2.0.0/safeai/frameworks/semantic_kernel}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/frameworks/semantic_kernel/parser.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/rules → safeai_static_analyzer-2.0.0/safeai/frameworks/windsurf}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/baseline.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/ci_context.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/enrich.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/exporter.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/fingerprints.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/identity.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/manifest.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/policy.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/registry/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/registry/connection.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/registry/persist.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/registry/queries.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/registry/schema.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/suppressions.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/kya/util.py +0 -0
- {safeai_static_analyzer-1.9.1/safeai/scoring → safeai_static_analyzer-2.0.0/safeai/report}/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/html_kit.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/json_report.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/pr_comment.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/registry_html.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/report/terminal.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/rules/loader.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/scorecard.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/scoring/engine.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/severity.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/telemetry/__init__.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/telemetry/client.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/telemetry/config.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/telemetry/schema.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/setup.cfg +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_assurance.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_baseline.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_capability_detection.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_capability_diff_v2.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_ci_context.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_component_graph.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_component_hash.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_confidence_arbitration.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_crewai_framework.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_env_dependency.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_escalation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_fingerprints.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_github_action.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_html_report.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_init_and_rules.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_kya_integration.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_kya_manifest.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_langgraph_framework.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_llamaindex_framework.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_mcp_validation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_n8n_workflow.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_parser_aggregation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_phase15.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_policy.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_pr_comment.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_pr_comment_cli.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_registry.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_registry_cli.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_registry_html.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_release_fixes.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_release_validation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_rules_loader_determinism.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_sarif.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_scan_phase11.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_scorecard.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_scoring.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_semantic.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_severity.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_suppressions.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_target_taxonomy_engine.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_telemetry.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_tool_identity.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_tool_implementation.py +0 -0
- {safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SafeAI-Static-Analyzer
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
|
@@ -37,6 +37,8 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v1.9.0)
|
|
38
38
|
[](https://www.bestpractices.dev/en/projects/14126)
|
|
39
39
|
|
|
40
|
+
Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
|
|
41
|
+
|
|
40
42
|
**SafeAI** is a static analysis tool that scans AI application source code for security risks, capability exposure, and governance gaps. It runs entirely offline, never executes agents or calls LLMs, and integrates into CI/CD pipelines.
|
|
41
43
|
|
|
42
44
|
> 🌐 [safeai-analyzer.ikaruscareer.com](https://safeai-analyzer.ikaruscareer.com) — project landing page
|
|
@@ -80,7 +82,7 @@ SafeAI sits before runtime guardrails and red-teaming tools in the security life
|
|
|
80
82
|
|
|
81
83
|
| Feature | Description |
|
|
82
84
|
|---------|-------------|
|
|
83
|
-
| **Framework Detection** | Detects and parses
|
|
85
|
+
| **Framework Detection** | Detects and parses 16 AI agent frameworks (AST + config + regex, no mutual exclusion) |
|
|
84
86
|
| **Tool Identity & Access Modes** | Capabilities attributed to named tools (agent / MCP server / skill / tool / workflow node) on an access scale `none < read < write < mutate < execute`; inferred modes are flagged, never overstated |
|
|
85
87
|
| **Capability Discovery** | Maps 19 capability categories (shell, filesystem, network, database, memory, MCP, ...) with evidence, confidence, and provenance |
|
|
86
88
|
| **Capability Escalation Detection** | Per-tool authority diffs between scans (new shell, read→write widening, new MCP server, removed approval gate, ...) — 14 rules, including gating-aware subsumption |
|
|
@@ -116,7 +118,7 @@ File Collection — Python, YAML, JSON, .prompt, and .claude configs;
|
|
|
116
118
|
prunes VCS, caches, oversized files, and SafeAI's own artifacts
|
|
117
119
|
│
|
|
118
120
|
▼
|
|
119
|
-
Framework Detection —
|
|
121
|
+
Framework Detection — 16 parsers (AST + config + regex), all run on all files;
|
|
120
122
|
import graph and dependency manifests
|
|
121
123
|
│
|
|
122
124
|
▼
|
|
@@ -165,13 +167,14 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
165
167
|
| LlamaIndex | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
166
168
|
| Dify | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
167
169
|
| n8n | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
170
|
+
| Cursor (.cursorrules) | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
168
171
|
|
|
169
172
|
|
|
170
173
|
### Framework Support Details
|
|
171
174
|
|
|
172
175
|
- **LangGraph** — detects `StateGraph`, `add_edge`, `bind_tools`, nodes, models
|
|
173
176
|
- **CrewAI** — detects `Agent`, `Task`, tools, models
|
|
174
|
-
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`,
|
|
177
|
+
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`, `register_function`, models
|
|
175
178
|
- **LangChain** — detects `AgentExecutor`, `Chain`, `Tool`, `PromptTemplate`, models
|
|
176
179
|
- **Semantic Kernel** — detects `Kernel.invoke`, plugins, functions, skills, memory
|
|
177
180
|
- **OpenAI Agents SDK** — detects `Agent`, tools, handoffs, MCP references
|
|
@@ -186,6 +189,9 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
186
189
|
- **LlamaIndex** — detects agents, tools, indexes, and model references
|
|
187
190
|
- **Dify** — detects Dify workflow and agent configuration files
|
|
188
191
|
- **n8n** — detects n8n workflow exports, nodes, and connections
|
|
192
|
+
- **Cursor (.cursorrules)** — detects declared tools/permissions and
|
|
193
|
+
capability-relevant keywords (shell, filesystem, HTTP, database) in the
|
|
194
|
+
IDE's rules config, JSON, YAML, or free text
|
|
189
195
|
|
|
190
196
|
Maturity is on the scale defined in [`FRAMEWORK_SUPPORT.md`](FRAMEWORK_SUPPORT.md):
|
|
191
197
|
**Partial** = reliable detection and discovery with capability/risk analysis over
|
|
@@ -761,10 +767,19 @@ See [ROADMAP.md](./ROADMAP.md) for the detailed roadmap.
|
|
|
761
767
|
SafeAI is built by and for the AI security community. Thank you to all
|
|
762
768
|
contributors who have helped make AI safer:
|
|
763
769
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
770
|
+
| Contributor | Key Contributions |
|
|
771
|
+
|-------------|-------------------|
|
|
772
|
+
| [@i-safonoff](https://github.com/i-safonoff) | `.cursorrules` framework adapter, `rule_coverage_summary()`, RULES_REFERENCE.md, dataflow casing fix |
|
|
773
|
+
| [@ARAVIND281](https://github.com/ARAVIND281) | Claude Code permission evaluation order, interprocedural data-flow tracking |
|
|
774
|
+
| [@Solarthis](https://github.com/Solarthis) | MCP tool description injection detection |
|
|
775
|
+
| [@Aming9303](https://github.com/Aming9303) | `safeai registry components` CLI, `safeai init` command, GitHub Actions example |
|
|
776
|
+
| [@adnqcr7-code](https://github.com/adnqcr7-code) | Framework detection tests (LangGraph, CrewAI, LlamaIndex, n8n, Claude Code), CI/SARIF docs |
|
|
777
|
+
| [@hadbiaghiles](https://github.com/hadbiaghiles) | AutoGen framework documentation |
|
|
778
|
+
| [@D05TL3](https://github.com/D05TL3) | GitHub Actions scanning example |
|
|
779
|
+
| [@mikemikimike](https://github.com/mikemikimike) | Adapter negative detection tests |
|
|
780
|
+
| [@mah](https://github.com/mahirhir) | Claude Code deep analysis documentation |
|
|
781
|
+
| [@asarakhatun17-lgtm](https://github.com/asarakhatun17-lgtm) | Supported frameworks consistency fix |
|
|
782
|
+
| [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
|
|
768
783
|
|
|
769
784
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
|
|
770
785
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v1.9.0)
|
|
7
7
|
[](https://www.bestpractices.dev/en/projects/14126)
|
|
8
8
|
|
|
9
|
+
Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
|
|
10
|
+
|
|
9
11
|
**SafeAI** is a static analysis tool that scans AI application source code for security risks, capability exposure, and governance gaps. It runs entirely offline, never executes agents or calls LLMs, and integrates into CI/CD pipelines.
|
|
10
12
|
|
|
11
13
|
> 🌐 [safeai-analyzer.ikaruscareer.com](https://safeai-analyzer.ikaruscareer.com) — project landing page
|
|
@@ -49,7 +51,7 @@ SafeAI sits before runtime guardrails and red-teaming tools in the security life
|
|
|
49
51
|
|
|
50
52
|
| Feature | Description |
|
|
51
53
|
|---------|-------------|
|
|
52
|
-
| **Framework Detection** | Detects and parses
|
|
54
|
+
| **Framework Detection** | Detects and parses 16 AI agent frameworks (AST + config + regex, no mutual exclusion) |
|
|
53
55
|
| **Tool Identity & Access Modes** | Capabilities attributed to named tools (agent / MCP server / skill / tool / workflow node) on an access scale `none < read < write < mutate < execute`; inferred modes are flagged, never overstated |
|
|
54
56
|
| **Capability Discovery** | Maps 19 capability categories (shell, filesystem, network, database, memory, MCP, ...) with evidence, confidence, and provenance |
|
|
55
57
|
| **Capability Escalation Detection** | Per-tool authority diffs between scans (new shell, read→write widening, new MCP server, removed approval gate, ...) — 14 rules, including gating-aware subsumption |
|
|
@@ -85,7 +87,7 @@ File Collection — Python, YAML, JSON, .prompt, and .claude configs;
|
|
|
85
87
|
prunes VCS, caches, oversized files, and SafeAI's own artifacts
|
|
86
88
|
│
|
|
87
89
|
▼
|
|
88
|
-
Framework Detection —
|
|
90
|
+
Framework Detection — 16 parsers (AST + config + regex), all run on all files;
|
|
89
91
|
import graph and dependency manifests
|
|
90
92
|
│
|
|
91
93
|
▼
|
|
@@ -134,13 +136,14 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
134
136
|
| LlamaIndex | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
135
137
|
| Dify | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
136
138
|
| n8n | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
139
|
+
| Cursor (.cursorrules) | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
137
140
|
|
|
138
141
|
|
|
139
142
|
### Framework Support Details
|
|
140
143
|
|
|
141
144
|
- **LangGraph** — detects `StateGraph`, `add_edge`, `bind_tools`, nodes, models
|
|
142
145
|
- **CrewAI** — detects `Agent`, `Task`, tools, models
|
|
143
|
-
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`,
|
|
146
|
+
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`, `register_function`, models
|
|
144
147
|
- **LangChain** — detects `AgentExecutor`, `Chain`, `Tool`, `PromptTemplate`, models
|
|
145
148
|
- **Semantic Kernel** — detects `Kernel.invoke`, plugins, functions, skills, memory
|
|
146
149
|
- **OpenAI Agents SDK** — detects `Agent`, tools, handoffs, MCP references
|
|
@@ -155,6 +158,9 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
155
158
|
- **LlamaIndex** — detects agents, tools, indexes, and model references
|
|
156
159
|
- **Dify** — detects Dify workflow and agent configuration files
|
|
157
160
|
- **n8n** — detects n8n workflow exports, nodes, and connections
|
|
161
|
+
- **Cursor (.cursorrules)** — detects declared tools/permissions and
|
|
162
|
+
capability-relevant keywords (shell, filesystem, HTTP, database) in the
|
|
163
|
+
IDE's rules config, JSON, YAML, or free text
|
|
158
164
|
|
|
159
165
|
Maturity is on the scale defined in [`FRAMEWORK_SUPPORT.md`](FRAMEWORK_SUPPORT.md):
|
|
160
166
|
**Partial** = reliable detection and discovery with capability/risk analysis over
|
|
@@ -730,10 +736,19 @@ See [ROADMAP.md](./ROADMAP.md) for the detailed roadmap.
|
|
|
730
736
|
SafeAI is built by and for the AI security community. Thank you to all
|
|
731
737
|
contributors who have helped make AI safer:
|
|
732
738
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
739
|
+
| Contributor | Key Contributions |
|
|
740
|
+
|-------------|-------------------|
|
|
741
|
+
| [@i-safonoff](https://github.com/i-safonoff) | `.cursorrules` framework adapter, `rule_coverage_summary()`, RULES_REFERENCE.md, dataflow casing fix |
|
|
742
|
+
| [@ARAVIND281](https://github.com/ARAVIND281) | Claude Code permission evaluation order, interprocedural data-flow tracking |
|
|
743
|
+
| [@Solarthis](https://github.com/Solarthis) | MCP tool description injection detection |
|
|
744
|
+
| [@Aming9303](https://github.com/Aming9303) | `safeai registry components` CLI, `safeai init` command, GitHub Actions example |
|
|
745
|
+
| [@adnqcr7-code](https://github.com/adnqcr7-code) | Framework detection tests (LangGraph, CrewAI, LlamaIndex, n8n, Claude Code), CI/SARIF docs |
|
|
746
|
+
| [@hadbiaghiles](https://github.com/hadbiaghiles) | AutoGen framework documentation |
|
|
747
|
+
| [@D05TL3](https://github.com/D05TL3) | GitHub Actions scanning example |
|
|
748
|
+
| [@mikemikimike](https://github.com/mikemikimike) | Adapter negative detection tests |
|
|
749
|
+
| [@mah](https://github.com/mahirhir) | Claude Code deep analysis documentation |
|
|
750
|
+
| [@asarakhatun17-lgtm](https://github.com/asarakhatun17-lgtm) | Supported frameworks consistency fix |
|
|
751
|
+
| [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
|
|
737
752
|
|
|
738
753
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
|
|
739
754
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SafeAI-Static-Analyzer
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
|
@@ -37,6 +37,8 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://github.com/ikaruscareer/SafeAI/releases/tag/v1.9.0)
|
|
38
38
|
[](https://www.bestpractices.dev/en/projects/14126)
|
|
39
39
|
|
|
40
|
+
Enjoying SafeAI? A ⭐ on [GitHub](https://github.com/ikaruscareer/SafeAI) helps more security teams find it.
|
|
41
|
+
|
|
40
42
|
**SafeAI** is a static analysis tool that scans AI application source code for security risks, capability exposure, and governance gaps. It runs entirely offline, never executes agents or calls LLMs, and integrates into CI/CD pipelines.
|
|
41
43
|
|
|
42
44
|
> 🌐 [safeai-analyzer.ikaruscareer.com](https://safeai-analyzer.ikaruscareer.com) — project landing page
|
|
@@ -80,7 +82,7 @@ SafeAI sits before runtime guardrails and red-teaming tools in the security life
|
|
|
80
82
|
|
|
81
83
|
| Feature | Description |
|
|
82
84
|
|---------|-------------|
|
|
83
|
-
| **Framework Detection** | Detects and parses
|
|
85
|
+
| **Framework Detection** | Detects and parses 16 AI agent frameworks (AST + config + regex, no mutual exclusion) |
|
|
84
86
|
| **Tool Identity & Access Modes** | Capabilities attributed to named tools (agent / MCP server / skill / tool / workflow node) on an access scale `none < read < write < mutate < execute`; inferred modes are flagged, never overstated |
|
|
85
87
|
| **Capability Discovery** | Maps 19 capability categories (shell, filesystem, network, database, memory, MCP, ...) with evidence, confidence, and provenance |
|
|
86
88
|
| **Capability Escalation Detection** | Per-tool authority diffs between scans (new shell, read→write widening, new MCP server, removed approval gate, ...) — 14 rules, including gating-aware subsumption |
|
|
@@ -116,7 +118,7 @@ File Collection — Python, YAML, JSON, .prompt, and .claude configs;
|
|
|
116
118
|
prunes VCS, caches, oversized files, and SafeAI's own artifacts
|
|
117
119
|
│
|
|
118
120
|
▼
|
|
119
|
-
Framework Detection —
|
|
121
|
+
Framework Detection — 16 parsers (AST + config + regex), all run on all files;
|
|
120
122
|
import graph and dependency manifests
|
|
121
123
|
│
|
|
122
124
|
▼
|
|
@@ -165,13 +167,14 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
165
167
|
| LlamaIndex | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
166
168
|
| Dify | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
167
169
|
| n8n | ✔ | Partial | Minimal | Minimal | Experimental |
|
|
170
|
+
| Cursor (.cursorrules) | ✔ | Minimal | Minimal | Minimal | Experimental |
|
|
168
171
|
|
|
169
172
|
|
|
170
173
|
### Framework Support Details
|
|
171
174
|
|
|
172
175
|
- **LangGraph** — detects `StateGraph`, `add_edge`, `bind_tools`, nodes, models
|
|
173
176
|
- **CrewAI** — detects `Agent`, `Task`, tools, models
|
|
174
|
-
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`,
|
|
177
|
+
- **AutoGen** — detects `AssistantAgent`, `UserProxyAgent`, `register_for_llm`, `register_function`, models
|
|
175
178
|
- **LangChain** — detects `AgentExecutor`, `Chain`, `Tool`, `PromptTemplate`, models
|
|
176
179
|
- **Semantic Kernel** — detects `Kernel.invoke`, plugins, functions, skills, memory
|
|
177
180
|
- **OpenAI Agents SDK** — detects `Agent`, tools, handoffs, MCP references
|
|
@@ -186,6 +189,9 @@ Registry & Reports — shared SQLite registry; terminal, JSON, SARIF 2.1.0, HTML
|
|
|
186
189
|
- **LlamaIndex** — detects agents, tools, indexes, and model references
|
|
187
190
|
- **Dify** — detects Dify workflow and agent configuration files
|
|
188
191
|
- **n8n** — detects n8n workflow exports, nodes, and connections
|
|
192
|
+
- **Cursor (.cursorrules)** — detects declared tools/permissions and
|
|
193
|
+
capability-relevant keywords (shell, filesystem, HTTP, database) in the
|
|
194
|
+
IDE's rules config, JSON, YAML, or free text
|
|
189
195
|
|
|
190
196
|
Maturity is on the scale defined in [`FRAMEWORK_SUPPORT.md`](FRAMEWORK_SUPPORT.md):
|
|
191
197
|
**Partial** = reliable detection and discovery with capability/risk analysis over
|
|
@@ -761,10 +767,19 @@ See [ROADMAP.md](./ROADMAP.md) for the detailed roadmap.
|
|
|
761
767
|
SafeAI is built by and for the AI security community. Thank you to all
|
|
762
768
|
contributors who have helped make AI safer:
|
|
763
769
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
770
|
+
| Contributor | Key Contributions |
|
|
771
|
+
|-------------|-------------------|
|
|
772
|
+
| [@i-safonoff](https://github.com/i-safonoff) | `.cursorrules` framework adapter, `rule_coverage_summary()`, RULES_REFERENCE.md, dataflow casing fix |
|
|
773
|
+
| [@ARAVIND281](https://github.com/ARAVIND281) | Claude Code permission evaluation order, interprocedural data-flow tracking |
|
|
774
|
+
| [@Solarthis](https://github.com/Solarthis) | MCP tool description injection detection |
|
|
775
|
+
| [@Aming9303](https://github.com/Aming9303) | `safeai registry components` CLI, `safeai init` command, GitHub Actions example |
|
|
776
|
+
| [@adnqcr7-code](https://github.com/adnqcr7-code) | Framework detection tests (LangGraph, CrewAI, LlamaIndex, n8n, Claude Code), CI/SARIF docs |
|
|
777
|
+
| [@hadbiaghiles](https://github.com/hadbiaghiles) | AutoGen framework documentation |
|
|
778
|
+
| [@D05TL3](https://github.com/D05TL3) | GitHub Actions scanning example |
|
|
779
|
+
| [@mikemikimike](https://github.com/mikemikimike) | Adapter negative detection tests |
|
|
780
|
+
| [@mah](https://github.com/mahirhir) | Claude Code deep analysis documentation |
|
|
781
|
+
| [@asarakhatun17-lgtm](https://github.com/asarakhatun17-lgtm) | Supported frameworks consistency fix |
|
|
782
|
+
| [@yugaaank](https://github.com/yugaaank) | Capability detectors (Docker, Kubernetes, Redis, S3, GCP, Slack, Jira, browser) |
|
|
768
783
|
|
|
769
784
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved.
|
|
770
785
|
|
|
@@ -81,6 +81,8 @@ safeai/frameworks/claude_code/permissions.py
|
|
|
81
81
|
safeai/frameworks/claude_code/settings.py
|
|
82
82
|
safeai/frameworks/crewai/__init__.py
|
|
83
83
|
safeai/frameworks/crewai/parser.py
|
|
84
|
+
safeai/frameworks/cursorrules/__init__.py
|
|
85
|
+
safeai/frameworks/cursorrules/parser.py
|
|
84
86
|
safeai/frameworks/dify/__init__.py
|
|
85
87
|
safeai/frameworks/dify/parser.py
|
|
86
88
|
safeai/frameworks/google_adk/__init__.py
|
|
@@ -103,6 +105,8 @@ safeai/frameworks/openai_agents/__init__.py
|
|
|
103
105
|
safeai/frameworks/openai_agents/parser.py
|
|
104
106
|
safeai/frameworks/semantic_kernel/__init__.py
|
|
105
107
|
safeai/frameworks/semantic_kernel/parser.py
|
|
108
|
+
safeai/frameworks/windsurf/__init__.py
|
|
109
|
+
safeai/frameworks/windsurf/parser.py
|
|
106
110
|
safeai/kya/__init__.py
|
|
107
111
|
safeai/kya/assurance.py
|
|
108
112
|
safeai/kya/baseline.py
|
|
@@ -121,6 +125,7 @@ safeai/kya/registry/persist.py
|
|
|
121
125
|
safeai/kya/registry/queries.py
|
|
122
126
|
safeai/kya/registry/schema.py
|
|
123
127
|
safeai/report/__init__.py
|
|
128
|
+
safeai/report/failure_matrix.py
|
|
124
129
|
safeai/report/html.py
|
|
125
130
|
safeai/report/html_kit.py
|
|
126
131
|
safeai/report/json_report.py
|
|
@@ -149,8 +154,10 @@ tests/test_component_hash.py
|
|
|
149
154
|
tests/test_confidence_arbitration.py
|
|
150
155
|
tests/test_control_mappings.py
|
|
151
156
|
tests/test_crewai_framework.py
|
|
157
|
+
tests/test_cursorrules_framework.py
|
|
152
158
|
tests/test_env_dependency.py
|
|
153
159
|
tests/test_escalation.py
|
|
160
|
+
tests/test_evidence_type.py
|
|
154
161
|
tests/test_fingerprints.py
|
|
155
162
|
tests/test_github_action.py
|
|
156
163
|
tests/test_governance_analyzer.py
|
|
@@ -160,6 +167,7 @@ tests/test_kya_integration.py
|
|
|
160
167
|
tests/test_kya_manifest.py
|
|
161
168
|
tests/test_langgraph_framework.py
|
|
162
169
|
tests/test_llamaindex_framework.py
|
|
170
|
+
tests/test_mcp_tool_poisoning.py
|
|
163
171
|
tests/test_mcp_validation.py
|
|
164
172
|
tests/test_n8n_workflow.py
|
|
165
173
|
tests/test_parser_aggregation.py
|
|
@@ -184,4 +192,5 @@ tests/test_target_taxonomy_engine.py
|
|
|
184
192
|
tests/test_telemetry.py
|
|
185
193
|
tests/test_tool_identity.py
|
|
186
194
|
tests/test_tool_implementation.py
|
|
187
|
-
tests/test_version.py
|
|
195
|
+
tests/test_version.py
|
|
196
|
+
tests/test_windsurf_framework.py
|
|
@@ -72,6 +72,7 @@ CATEGORY_BY_CAP = {
|
|
|
72
72
|
def _finding(rule_id, rule, message, path, line, capability, framework="generic", evidence=None, confidence=0.6, score_contribution=8):
|
|
73
73
|
return {
|
|
74
74
|
"rule_id": rule_id,
|
|
75
|
+
"evidence_type": "static-pattern", # #94 - regex fallback pass over raw file content
|
|
75
76
|
"severity": rule.get("severity", "medium"),
|
|
76
77
|
"message": message,
|
|
77
78
|
"file": path,
|
|
@@ -160,6 +161,7 @@ class CapabilityAnalyzer:
|
|
|
160
161
|
|
|
161
162
|
findings.append({
|
|
162
163
|
"rule_id": rule_id,
|
|
164
|
+
"evidence_type": "static-pattern", # #94 - regex fallback pass over raw file content
|
|
163
165
|
"severity": rule.get("severity", "medium"),
|
|
164
166
|
"message": f"Capability discovered: {cap_name}",
|
|
165
167
|
"file": path,
|
|
@@ -249,6 +251,7 @@ class CapabilityAnalyzer:
|
|
|
249
251
|
rule = rule_map.get("CAP_AUTONOMY", {})
|
|
250
252
|
findings.append({
|
|
251
253
|
"rule_id": "CAP_AUTONOMY",
|
|
254
|
+
"evidence_type": "static-pattern", # #94 - regex fallback pass over raw file content
|
|
252
255
|
"severity": rule.get("severity", "high"),
|
|
253
256
|
"message": "Potential autonomous agent loop detected",
|
|
254
257
|
"file": path,
|
|
@@ -22,8 +22,9 @@ _REMEDIATION = {
|
|
|
22
22
|
"command or path pattern the workflow needs.",
|
|
23
23
|
"CC_BYPASS_PERMISSIONS": "Remove the bypass/auto-approve setting and keep the "
|
|
24
24
|
"human approval gate enabled in committed settings.",
|
|
25
|
-
"CC_DENY_SHADOWED": "
|
|
26
|
-
"
|
|
25
|
+
"CC_DENY_SHADOWED": "Remove the allow entry that cannot apply, or narrow the "
|
|
26
|
+
"deny entry if the allow was the intended behaviour. The "
|
|
27
|
+
"deny is what takes effect.",
|
|
27
28
|
"CC_FS_WRITE_OUTSIDE_ROOT": "Constrain write grants to paths inside the project root.",
|
|
28
29
|
"CC_SLASH_COMMAND_SHELL": "Pin the command, avoid inline shell in stored "
|
|
29
30
|
"instructions, and review who can invoke it.",
|
|
@@ -98,6 +99,7 @@ def _finding(rule_id, rule_map, message, path, line, evidence, reason, severity=
|
|
|
98
99
|
resolved = severity or rule.get("severity") or _DEFAULT_SEVERITY.get(rule_id, "medium")
|
|
99
100
|
return {
|
|
100
101
|
"rule_id": rule_id,
|
|
102
|
+
"evidence_type": "static-config", # #94 - reads declared permission/hook/settings entries
|
|
101
103
|
"severity": resolved,
|
|
102
104
|
"message": message,
|
|
103
105
|
"file": path,
|
|
@@ -204,15 +206,27 @@ class ClaudeCodeAnalyzer:
|
|
|
204
206
|
|
|
205
207
|
def _permission_findings(self, records, rule_map):
|
|
206
208
|
findings = []
|
|
209
|
+
effective = cc_permissions.resolve_effective_rules(records)
|
|
207
210
|
for record in records:
|
|
208
211
|
if record["decision"] == "allow" and record["wildcard"] and record["tool"]:
|
|
212
|
+
# #93 - a wildcard allow grants nothing the tool's denies do not
|
|
213
|
+
# already take back, at any scope. Report it, but not as though
|
|
214
|
+
# the surface were unbounded when it demonstrably is not.
|
|
215
|
+
constrained = effective.get(str(record["tool"]).lower(), {}).get("decision") == "deny"
|
|
209
216
|
findings.append(_finding(
|
|
210
217
|
"CC_WILDCARD_PERMISSION", rule_map,
|
|
211
218
|
f"Unconstrained Claude Code permission: {record['entry']}",
|
|
212
219
|
record["path"], record["line"],
|
|
213
220
|
evidence=record["entry"],
|
|
214
|
-
reason=
|
|
215
|
-
|
|
221
|
+
reason=(
|
|
222
|
+
f"{record['tool']} is allowed with no argument constraint, but a deny "
|
|
223
|
+
f"rule for the same tool is evaluated first, so the granted surface is "
|
|
224
|
+
f"bounded by that deny."
|
|
225
|
+
if constrained else
|
|
226
|
+
f"{record['tool']} is allowed with no argument constraint, granting "
|
|
227
|
+
f"{record['access_mode']} authority over its entire surface."
|
|
228
|
+
),
|
|
229
|
+
severity="medium" if constrained else None,
|
|
216
230
|
))
|
|
217
231
|
if cc_permissions.writes_outside_root(record):
|
|
218
232
|
findings.append(_finding(
|
|
@@ -225,14 +239,21 @@ class ClaudeCodeAnalyzer:
|
|
|
225
239
|
capability="Filesystem",
|
|
226
240
|
))
|
|
227
241
|
|
|
228
|
-
|
|
242
|
+
# #93 - this finding used to be stated the wrong way round. Claude Code
|
|
243
|
+
# evaluates deny, then ask, then allow, and "rule specificity doesn't
|
|
244
|
+
# change the order", so a matching deny ALWAYS decides the call. It is
|
|
245
|
+
# the allow that is dead configuration, not the deny, and the severity
|
|
246
|
+
# follows: contradictory rules are a maintenance problem, not a hole.
|
|
247
|
+
for allow, deny in cc_permissions.ineffective_allows(records):
|
|
229
248
|
findings.append(_finding(
|
|
230
249
|
"CC_DENY_SHADOWED", rule_map,
|
|
231
|
-
f"
|
|
232
|
-
|
|
233
|
-
evidence=f"
|
|
234
|
-
reason="
|
|
235
|
-
"
|
|
250
|
+
f"Allow entry can never apply, a deny covers it: {allow['entry']}",
|
|
251
|
+
allow["path"], allow["line"],
|
|
252
|
+
evidence=f"allow {allow['entry']} overridden by deny {deny['entry']}",
|
|
253
|
+
reason="Claude Code evaluates deny before allow and specificity does not change "
|
|
254
|
+
"that, so the deny decides every matching call and this allow widens "
|
|
255
|
+
"nothing. The deny is effective; the allow is dead configuration.",
|
|
256
|
+
severity="low",
|
|
236
257
|
))
|
|
237
258
|
return findings
|
|
238
259
|
|
{safeai_static_analyzer-1.9.1 → safeai_static_analyzer-2.0.0}/safeai/analyzers/dataflow/analyzer.py
RENAMED
|
@@ -6,8 +6,14 @@ file operations). Uses line-level proxy heuristics for propagation tracking.
|
|
|
6
6
|
|
|
7
7
|
This is a heuristic analysis — not a full interprocedural data-flow solver.
|
|
8
8
|
It provides a best-effort detection of common taint patterns in agent code.
|
|
9
|
+
|
|
10
|
+
Propagation is tracked within a function, and across ONE direct call to a
|
|
11
|
+
function defined in the same file (#96). Cross-file flows, recursion, dynamic
|
|
12
|
+
dispatch and calls made through an attribute or a variable remain out of
|
|
13
|
+
scope, and are reported as such in each finding's ``limitation`` field.
|
|
9
14
|
"""
|
|
10
15
|
|
|
16
|
+
import ast
|
|
11
17
|
import re
|
|
12
18
|
|
|
13
19
|
# Untrusted input sources
|
|
@@ -26,7 +32,12 @@ SINK_PATTERNS = {
|
|
|
26
32
|
"prompt": re.compile(r"\b(?:prompt|system_prompt|user_prompt|chat_history|messages\.append)\b", re.IGNORECASE),
|
|
27
33
|
"tool_call": re.compile(r"\b(?:tool_call|invoke_tool|call_tool|execute_tool)\b", re.IGNORECASE),
|
|
28
34
|
"shell": re.compile(r"\b(?:subprocess|os\.system|popen|exec\(|eval\()\b", re.IGNORECASE),
|
|
29
|
-
|
|
35
|
+
# The trailing \b this pattern used to carry could never match: it sat
|
|
36
|
+
# after the closing quote of the mode string, and the next character is
|
|
37
|
+
# always ')' — two non-word characters, so no boundary exists. The rule
|
|
38
|
+
# was unreachable, which is why #96's acceptance criterion could not be
|
|
39
|
+
# met without this. Found while wiring interprocedural tracking.
|
|
40
|
+
"file_write": re.compile(r"\bopen\([^)]*[\"'](?:w|a|x)[bt+]*[\"']", re.IGNORECASE),
|
|
30
41
|
"http_request": re.compile(r"\b(?:requests\.(?:get|post|put|delete)|httpx\.)\b", re.IGNORECASE),
|
|
31
42
|
"database": re.compile(r"\b(?:execute|cursor\.execute|query)\b", re.IGNORECASE),
|
|
32
43
|
}
|
|
@@ -134,10 +145,135 @@ def _track_propagation(content, sources, sinks):
|
|
|
134
145
|
return findings
|
|
135
146
|
|
|
136
147
|
|
|
148
|
+
def _same_file_call_graph(content):
|
|
149
|
+
"""Map same-file functions and the direct calls into them (#96).
|
|
150
|
+
|
|
151
|
+
Returns ``(functions, calls)`` where ``functions`` maps a function name to
|
|
152
|
+
its positional parameter names and body line span, and ``calls`` is a list
|
|
153
|
+
of direct call sites with the plain-name arguments they were given.
|
|
154
|
+
|
|
155
|
+
Deliberately shallow. Only ``def``/``async def`` at any nesting level, only
|
|
156
|
+
calls written as a bare name, and only arguments that are bare names are
|
|
157
|
+
considered — a call through an attribute (``obj.method(x)``), a variable
|
|
158
|
+
holding a function, or a computed argument is not resolved. That keeps the
|
|
159
|
+
pass conservative in the direction that matters: it can miss a real flow,
|
|
160
|
+
but it will not invent a callee.
|
|
161
|
+
|
|
162
|
+
Returns ``(None, None)`` when the file does not parse. The analyzer is run
|
|
163
|
+
over whatever is on disk, which includes files mid-edit and Python 2, and a
|
|
164
|
+
syntax error must not take the whole analyzer down.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
tree = ast.parse(content)
|
|
168
|
+
except (SyntaxError, ValueError, RecursionError):
|
|
169
|
+
return None, None
|
|
170
|
+
|
|
171
|
+
functions = {}
|
|
172
|
+
for node in ast.walk(tree):
|
|
173
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
174
|
+
end = getattr(node, "end_lineno", None)
|
|
175
|
+
if end is None: # pragma: no cover - Python < 3.8 shape
|
|
176
|
+
continue
|
|
177
|
+
functions[node.name] = {
|
|
178
|
+
"params": [a.arg for a in node.args.args],
|
|
179
|
+
"start": node.lineno,
|
|
180
|
+
"end": end,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
calls = []
|
|
184
|
+
for node in ast.walk(tree):
|
|
185
|
+
if not isinstance(node, ast.Call):
|
|
186
|
+
continue
|
|
187
|
+
if not isinstance(node.func, ast.Name):
|
|
188
|
+
continue
|
|
189
|
+
positional = [
|
|
190
|
+
arg.id if isinstance(arg, ast.Name) else None for arg in node.args
|
|
191
|
+
]
|
|
192
|
+
keywords = {
|
|
193
|
+
kw.arg: kw.value.id
|
|
194
|
+
for kw in node.keywords
|
|
195
|
+
if kw.arg and isinstance(kw.value, ast.Name)
|
|
196
|
+
}
|
|
197
|
+
calls.append({
|
|
198
|
+
"callee": node.func.id,
|
|
199
|
+
"line": node.lineno,
|
|
200
|
+
"positional": positional,
|
|
201
|
+
"keywords": keywords,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return functions, calls
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _track_interprocedural(content, sources, sinks):
|
|
208
|
+
"""Follow taint one call deep, within the same file (#96).
|
|
209
|
+
|
|
210
|
+
The intraprocedural pass requires the sink to appear *after* the source
|
|
211
|
+
line. That is wrong across a call boundary and not by a little: a helper is
|
|
212
|
+
usually defined ABOVE the code that calls it, so its sinks sit at lower line
|
|
213
|
+
numbers than the tainted argument. This pass therefore does not apply that
|
|
214
|
+
ordering rule to the callee body — the ordering that matters is
|
|
215
|
+
source-before-CALL, which is checked here.
|
|
216
|
+
|
|
217
|
+
One level only, and no recursion: a call inside the callee is not followed.
|
|
218
|
+
"""
|
|
219
|
+
functions, calls = _same_file_call_graph(content)
|
|
220
|
+
if not functions or not calls:
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
findings = []
|
|
224
|
+
for source in sources:
|
|
225
|
+
base_var = source["variable"].split(".")[0].split("(")[0]
|
|
226
|
+
if not base_var.isidentifier():
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
for call in calls:
|
|
230
|
+
# The taint has to exist before it can be passed.
|
|
231
|
+
if call["line"] < source["line"]:
|
|
232
|
+
continue
|
|
233
|
+
callee = functions.get(call["callee"])
|
|
234
|
+
if callee is None:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
# Which parameter did the tainted variable land on?
|
|
238
|
+
tainted_params = [
|
|
239
|
+
callee["params"][index]
|
|
240
|
+
for index, arg in enumerate(call["positional"])
|
|
241
|
+
if arg == base_var and index < len(callee["params"])
|
|
242
|
+
]
|
|
243
|
+
tainted_params += [
|
|
244
|
+
name for name, value in call["keywords"].items()
|
|
245
|
+
if value == base_var and name in callee["params"]
|
|
246
|
+
]
|
|
247
|
+
if not tainted_params:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
for sink in sinks:
|
|
251
|
+
if not (callee["start"] <= sink["line"] <= callee["end"]):
|
|
252
|
+
continue
|
|
253
|
+
if not any(
|
|
254
|
+
re.search(rf"\b{re.escape(param)}\b", sink["sink_text"])
|
|
255
|
+
for param in tainted_params
|
|
256
|
+
):
|
|
257
|
+
continue
|
|
258
|
+
findings.append({
|
|
259
|
+
"source_line": source["line"],
|
|
260
|
+
"source_var": base_var,
|
|
261
|
+
"sink_line": sink["line"],
|
|
262
|
+
"sink_type": sink["sink_type"],
|
|
263
|
+
"source_text": source["source_text"],
|
|
264
|
+
"sink_text": sink["sink_text"],
|
|
265
|
+
"via_call": call["callee"],
|
|
266
|
+
"call_line": call["line"],
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
return findings
|
|
270
|
+
|
|
271
|
+
|
|
137
272
|
def _finding(rule_id, rule, message, path, line, evidence=None):
|
|
138
273
|
"""Create a data-flow finding dict, deriving severity/owasp from the rule."""
|
|
139
274
|
return {
|
|
140
275
|
"rule_id": rule_id,
|
|
276
|
+
"evidence_type": "static-pattern", # #94 - line-level taint heuristic over source text
|
|
141
277
|
"severity": rule.get("severity", "high"),
|
|
142
278
|
"message": message,
|
|
143
279
|
"file": path,
|
|
@@ -155,9 +291,11 @@ def _finding(rule_id, rule, message, path, line, evidence=None):
|
|
|
155
291
|
"confidence": "heuristic",
|
|
156
292
|
"scope": "static-analysis",
|
|
157
293
|
"limitation": (
|
|
158
|
-
"Line-level proxy heuristic
|
|
159
|
-
"
|
|
160
|
-
"
|
|
294
|
+
"Line-level proxy heuristic. Follows taint one direct call deep "
|
|
295
|
+
"within a single file (#96); does NOT cross files, follow "
|
|
296
|
+
"recursion, resolve dynamic dispatch, or resolve a call made "
|
|
297
|
+
"through an attribute or a variable. May miss indirect "
|
|
298
|
+
"propagation or produce false positives on dynamic constructs."
|
|
161
299
|
),
|
|
162
300
|
}
|
|
163
301
|
|
|
@@ -193,6 +331,9 @@ class DataFlowAnalyzer:
|
|
|
193
331
|
continue
|
|
194
332
|
|
|
195
333
|
propagations = _track_propagation(content, sources, sinks)
|
|
334
|
+
# #96 - one call deep, same file. Appended rather than merged: the
|
|
335
|
+
# dedupe key below already collapses a flow both passes find.
|
|
336
|
+
propagations += _track_interprocedural(content, sources, sinks)
|
|
196
337
|
|
|
197
338
|
for prop in propagations:
|
|
198
339
|
key = (path, prop["source_line"], prop["sink_line"], prop["sink_type"])
|
|
@@ -200,7 +341,7 @@ class DataFlowAnalyzer:
|
|
|
200
341
|
continue
|
|
201
342
|
seen.add(key)
|
|
202
343
|
|
|
203
|
-
rule_id = f"DATAFLOW_{prop['sink_type']
|
|
344
|
+
rule_id = f"DATAFLOW_{prop['sink_type']}"
|
|
204
345
|
rule = rule_map.get(rule_id, {})
|
|
205
346
|
findings.append(_finding(
|
|
206
347
|
rule_id=rule_id,
|
|
@@ -213,7 +354,15 @@ class DataFlowAnalyzer:
|
|
|
213
354
|
),
|
|
214
355
|
path=path,
|
|
215
356
|
line=prop["sink_line"],
|
|
216
|
-
evidence=
|
|
357
|
+
evidence=(
|
|
358
|
+
f"source:{prop['source_var']}@L{prop['source_line']} -> "
|
|
359
|
+
+ (
|
|
360
|
+
f"call:{prop['via_call']}()@L{prop['call_line']} -> "
|
|
361
|
+
if prop.get("via_call")
|
|
362
|
+
else ""
|
|
363
|
+
)
|
|
364
|
+
+ f"sink:{prop['sink_type']}@L{prop['sink_line']}"
|
|
365
|
+
),
|
|
217
366
|
))
|
|
218
367
|
|
|
219
368
|
return findings
|
|
@@ -186,6 +186,7 @@ def _wrap_inventory(inventory, rules):
|
|
|
186
186
|
rule = rule_map.get(INVENTORY_RULE_ID, {})
|
|
187
187
|
return [{
|
|
188
188
|
"rule_id": INVENTORY_RULE_ID,
|
|
189
|
+
"evidence_type": "static-config", # #94 - inventories declared config references, names only
|
|
189
190
|
"severity": rule.get("severity", "info"),
|
|
190
191
|
"message": f"Referenced {len(inventory)} external configuration/credential names",
|
|
191
192
|
"file": "<scan>",
|