Class: Omamori::CoreRunner
- Inherits:
-
Object
- Object
- Omamori::CoreRunner
- Defined in:
- lib/omamori/core_runner.rb
Constant Summary collapse
- JSON_SCHEMA =
Define the JSON Schema for Structured Output
{ "type": "object", "properties": { "security_risks": { "type": "array", "description": "検出されたセキュリティリスクのリスト。", "items": { "type": "object", "properties": { "type": { "type": "string", "description": "検出されたリスクの種類 (例: XSS, CSRF, IDORなど)。3.3の診断対象脆弱性リストのいずれかであること。" }, "location": { "type": "string", "description": "リスクが存在するコードのファイル名、行番号、またはコードスニペット。差分分析の場合は差分の該当箇所を示す形式 (例: ファイル名:+行番号) であること。" }, "details": { "type": "string", "description": "リスクの詳細な説明と、なぜそれがリスクなのかの理由。" }, "severity": { "type": "string", "description": "リスクの深刻度。", "enum": ["Critical", "High", "Medium", "Low", "Info"] }, "code_snippet": { "type": "string", "description": "該当するコードスニペット。" } }, "required": ["type", "location", "details", "severity"] } } }, "required": ["security_risks"] }.freeze
- DEFAULT_RISKS_TO_CHECK =
Default risks to check, can be overridden by config
[ :xss, :csrf, :idor, :open_redirect, :ssrf, :session_fixation # TODO: Add other risks from requirements based on PromptManager::RISK_PROMPTS.keys ].freeze
- DEFAULT_SPLIT_THRESHOLD =
Threshold for splitting large content (characters as a proxy for tokens) Can be overridden by config
8000
Instance Method Summary collapse
-
#initialize(args) ⇒ CoreRunner
constructor
Characters.
- #run ⇒ Object
Constructor Details
#initialize(args) ⇒ CoreRunner
Characters
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/omamori/core_runner.rb', line 68 def initialize(args) @args = args @options = { command: :scan, format: :console } # Default command and format @target_paths = [] @config = Omamori::Config.new # Initialize components with configuration api_key = @config.get("api_key", ENV["GEMINI_API_KEY"]) gemini_model = @config.get("model", "gemini-2.5-flash-preview-04-17") @gemini_client = AIAnalysisEngine::GeminiClient.new(api_key) @prompt_manager = AIAnalysisEngine::PromptManager.new(@config) chunk_size = @config.get("chunk_size", DEFAULT_SPLIT_THRESHOLD) @diff_splitter = AIAnalysisEngine::DiffSplitter.new(chunk_size: chunk_size) report_config = @config.get("report", {}) report_output_path = report_config.fetch("output_path", "./omamori_report") html_template_path = report_config.fetch("html_template", nil) @console_formatter = ReportGenerator::ConsoleFormatter.new @html_formatter = ReportGenerator::HTMLFormatter.new(report_output_path, html_template_path) @json_formatter = ReportGenerator::JSONFormatter.new(report_output_path) static_analyser_config = @config.get("static_analysers", {}) = static_analyser_config.fetch("brakeman", {}).fetch("options", {}) = static_analyser_config.fetch("bundler_audit", {}).fetch("options", {}) @brakeman_runner = StaticAnalysers::BrakemanRunner.new() @bundler_audit_runner = StaticAnalysers::BundlerAuditRunner.new() end |
Instance Method Details
#run ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/omamori/core_runner.rb', line 98 def run case @options[:command] when :scan # Initialize results ai_analysis_result = { "security_risks" => [] } brakeman_result = nil bundler_audit_result = nil # Run static analysers first unless --ai option is specified unless @options[:only_ai] puts "Running static analysers..." brakeman_result = @brakeman_runner.run bundler_audit_result = @bundler_audit_runner.run end # Perform AI analysis based on scan mode case @options[:scan_mode] when :paths # Scan specified files/directories if @target_paths.empty? puts "No paths specified for scan. Use --diff, --all, or provide paths." else puts "Scanning specified paths with AI..." ignore_patterns = @config.ignore_patterns force_scan_ignored = @options.fetch(:force_scan_ignored, false) files_to_scan = collect_files_from_paths(@target_paths, ignore_patterns, force_scan_ignored) if files_to_scan.empty? puts "No Ruby files found in the specified paths." else files_to_scan.each do |file_path| begin file_content = File.read(file_path) puts "Analyzing file: #{file_path}..." # スキャン中のファイルパスを表示 current_risks_to_check = get_risks_to_check # @diff_splitterのインスタンス変数 @chunk_size を参照して比較 if file_content.length > @diff_splitter.instance_variable_get(:@chunk_size) puts "File content exceeds threshold (#{@diff_splitter.instance_variable_get(:@chunk_size)} chars), splitting..." file_ai_result = @diff_splitter.process_in_chunks(file_content, @gemini_client, JSON_SCHEMA, @prompt_manager, current_risks_to_check, file_path: file_path, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) else prompt = @prompt_manager.build_prompt(file_content, current_risks_to_check, JSON_SCHEMA, file_path: file_path) file_ai_result = @gemini_client.analyze(prompt, JSON_SCHEMA, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) end # Merge results if file_ai_result && file_ai_result["security_risks"] ai_analysis_result["security_risks"].concat(file_ai_result["security_risks"]) end rescue => e puts "Error analyzing file #{file_path}: #{e.}" end end end end when :diff # Scan staged differences diff_content = get_staged_diff if diff_content.empty? puts "No staged changes to scan." else puts "Scanning staged differences with AI..." current_risks_to_check = get_risks_to_check # @diff_splitterのインスタンス変数 @chunk_size を参照して比較 if diff_content.length > @diff_splitter.instance_variable_get(:@chunk_size) puts "Diff content exceeds threshold (#{@diff_splitter.instance_variable_get(:@chunk_size)} chars), splitting..." ai_analysis_result = @diff_splitter.process_in_chunks(diff_content, @gemini_client, JSON_SCHEMA, @prompt_manager, current_risks_to_check, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) else prompt = @prompt_manager.build_prompt(diff_content, current_risks_to_check, JSON_SCHEMA) ai_analysis_result = @gemini_client.analyze(prompt, JSON_SCHEMA, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) end end when :all # Scan entire codebase full_code_content = get_full_codebase if full_code_content.strip.empty? puts "No code found to scan." else puts "Scanning entire codebase with AI..." current_risks_to_check = get_risks_to_check # @diff_splitterのインスタンス変数 @chunk_size を参照して比較 if full_code_content.length > @diff_splitter.instance_variable_get(:@chunk_size) puts "Full code content exceeds threshold (#{@diff_splitter.instance_variable_get(:@chunk_size)} chars), splitting..." ai_analysis_result = @diff_splitter.process_in_chunks(full_code_content, @gemini_client, JSON_SCHEMA, @prompt_manager, current_risks_to_check, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) else prompt = @prompt_manager.build_prompt(full_code_content, current_risks_to_check, JSON_SCHEMA) ai_analysis_result = @gemini_client.analyze(prompt, JSON_SCHEMA, model: @config.get("model", "gemini-2.5-flash-preview-04-17")) end end else puts "Unknown scan mode: #{@options[:scan_mode]}" puts @opt_parser return # Exit if scan mode is invalid end # Combine results and display report ai_analysis_result ||= { "security_risks" => [] } # Ensure it's not nil combined_results = combine_results(ai_analysis_result, brakeman_result, bundler_audit_result) display_report(combined_results) puts "Scan complete." when :ci_setup generate_ci_setup(@options[:ci_service]) when :init generate_initial_files else puts "Unknown command: #{@options[:command]}" puts @opt_parser end end |