omamori 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +34 -14
  5. data/README.md +202 -49
  6. data/README_ja.md +204 -46
  7. data/demo/ai_analysis_vulnerability.rb +3 -3
  8. data/demo/csrf_vulnerability.rb +1 -1
  9. data/demo/eval_vulnerability.rb +1 -1
  10. data/demo/idor_vulnerability.rb +2 -2
  11. data/demo/insecure_cookie_vulnerability.rb +1 -1
  12. data/demo/open_redirect_vulnerability.rb +1 -1
  13. data/demo/static_analysis_vulnerability.rb +1 -1
  14. data/demo/xss_vulnerability.rb +1 -1
  15. data/exe/omamori +2 -2
  16. data/lib/omamori/ai_analysis_engine/checklist_scanner.rb +143 -0
  17. data/lib/omamori/ai_analysis_engine/cli_clients/base.rb +43 -0
  18. data/lib/omamori/ai_analysis_engine/cli_clients/claude_code_client.rb +35 -0
  19. data/lib/omamori/ai_analysis_engine/cli_clients/codex_client.rb +47 -0
  20. data/lib/omamori/ai_analysis_engine/diff_splitter.rb +30 -22
  21. data/lib/omamori/ai_analysis_engine/fallback_client.rb +23 -0
  22. data/lib/omamori/ai_analysis_engine/judge_client.rb +45 -0
  23. data/lib/omamori/ai_analysis_engine/llm_client.rb +71 -0
  24. data/lib/omamori/ai_analysis_engine/prompt_manager.rb +96 -19
  25. data/lib/omamori/ai_analysis_engine/ruby_llm_context.rb +23 -0
  26. data/lib/omamori/ai_analysis_engine/sub_checks/audit_log_missing.yml +12 -0
  27. data/lib/omamori/ai_analysis_engine/sub_checks/csrf.yml +22 -0
  28. data/lib/omamori/ai_analysis_engine/sub_checks/insecure_deserialization_rce.yml +9 -0
  29. data/lib/omamori/ai_analysis_engine/sub_checks/insufficient_encryption.yml +19 -0
  30. data/lib/omamori/ai_analysis_engine/sub_checks/mass_assignment.yml +19 -0
  31. data/lib/omamori/ai_analysis_engine/sub_checks/open_redirect.yml +17 -0
  32. data/lib/omamori/ai_analysis_engine/sub_checks/sql_injection.yml +20 -0
  33. data/lib/omamori/ai_analysis_engine/sub_checks/xss.yml +37 -0
  34. data/lib/omamori/ai_settings.rb +273 -0
  35. data/lib/omamori/config.rb +89 -69
  36. data/lib/omamori/core_runner.rb +485 -286
  37. data/lib/omamori/doctor.rb +95 -0
  38. data/lib/omamori/parallel.rb +24 -0
  39. data/lib/omamori/report_generator/console_formatter.rb +48 -42
  40. data/lib/omamori/report_generator/html_formatter.rb +6 -6
  41. data/lib/omamori/report_generator/json_formatter.rb +1 -1
  42. data/lib/omamori/setup_files.rb +260 -0
  43. data/lib/omamori/static_analysers/brakeman_runner.rb +7 -7
  44. data/lib/omamori/static_analysers/bundler_audit_runner.rb +8 -6
  45. data/lib/omamori/version.rb +2 -2
  46. data/lib/omamori.rb +11 -11
  47. metadata +61 -14
  48. data/lib/omamori/ai_analysis_engine/gemini_client.rb +0 -71
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Omamori
6
+ module AIAnalysisEngine
7
+ module CLIClients
8
+ # `claude -p` (Claude Code) with all tools disabled and structured output.
9
+ class ClaudeCodeClient < Base
10
+ private
11
+
12
+ def default_command
13
+ 'claude'
14
+ end
15
+
16
+ def build_command(_dir, json_schema, model)
17
+ command = [@command, '-p', '--tools', '', '--strict-mcp-config', '--no-session-persistence',
18
+ '--output-format', 'json', '--json-schema', json_schema.to_json]
19
+ command += ['--model', model] if model
20
+ command
21
+ end
22
+
23
+ def parse(output, _dir)
24
+ response = JSON.parse(output)
25
+ if response['is_error']
26
+ puts "Warning: claude returned an error: #{response['result'].to_s[0, 500]}"
27
+ return nil
28
+ end
29
+
30
+ response['structured_output'] || JSON.parse(response['result'].to_s)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Omamori
6
+ module AIAnalysisEngine
7
+ module CLIClients
8
+ # `codex exec` in a read-only sandbox with an output schema.
9
+ class CodexClient < Base
10
+ private
11
+
12
+ def default_command
13
+ 'codex'
14
+ end
15
+
16
+ def build_command(dir, json_schema, model)
17
+ schema_path = File.join(dir, 'schema.json')
18
+ File.write(schema_path, JSON.generate(strict_schema(JSON.parse(json_schema.to_json))))
19
+ command = [@command, 'exec', '--sandbox', 'read-only', '--skip-git-repo-check', '--ephemeral',
20
+ '--output-schema', schema_path, '--output-last-message', File.join(dir, 'last_message.json')]
21
+ command += ['--model', model] if model
22
+ command + ['-']
23
+ end
24
+
25
+ def parse(_output, dir)
26
+ path = File.join(dir, 'last_message.json')
27
+ File.exist?(path) ? JSON.parse(File.read(path)) : nil
28
+ end
29
+
30
+ # OpenAI structured outputs require every property to be required and no additional properties
31
+ def strict_schema(schema)
32
+ case schema
33
+ when Hash
34
+ strict = schema.transform_values { |value| strict_schema(value) }
35
+ if strict['type'] == 'object' && strict['properties']
36
+ strict['required'] = strict['properties'].keys
37
+ strict['additionalProperties'] = false
38
+ end
39
+ strict
40
+ when Array then schema.map { |value| strict_schema(value) }
41
+ else schema
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,24 +1,32 @@
1
- # frozen_string_literal: true
1
+ # lib/omamori/ai_analysis_engine/diff_splitter.rb
2
+
3
+ require_relative 'llm_client'
2
4
 
3
5
  module Omamori
4
6
  module AIAnalysisEngine
5
7
  class DiffSplitter
6
- # TODO: Determine appropriate chunk size based on token limits
7
- # Gemini 1.5 Pro has a large context window (1 million tokens),
8
- # but splitting might still be necessary for very large inputs
9
- # or to manage cost/latency.
10
8
  DEFAULT_CHUNK_SIZE = 8000 # Characters as a proxy for tokens
11
9
 
12
10
  def initialize(chunk_size: DEFAULT_CHUNK_SIZE)
13
11
  @chunk_size = chunk_size
14
12
  end
15
13
 
14
+ # Splits a unified diff into [file_path, file_diff] pairs
15
+ def split_by_file(diff)
16
+ diff.split(/^(?=diff --git )/).filter_map do |file_diff|
17
+ next if file_diff.strip.empty?
18
+
19
+ path = file_diff[%r{\Adiff --git a/.+? b/(.+)$}, 1] || 'unknown'
20
+ [path, file_diff]
21
+ end
22
+ end
23
+
16
24
  def split(content)
17
25
  chunks = []
18
- current_chunk = ""
26
+ current_chunk = ''
19
27
  content.each_line do |line|
20
- if (current_chunk.length + line.length) > @chunk_size
21
- chunks << current_chunk unless current_chunk.empty?
28
+ if (current_chunk.length + line.length) > @chunk_size && !current_chunk.empty?
29
+ chunks << current_chunk
22
30
  current_chunk = line
23
31
  else
24
32
  current_chunk += line
@@ -28,35 +36,35 @@ module Omamori
28
36
  chunks
29
37
  end
30
38
 
31
- def process_in_chunks(content, gemini_client, json_schema, prompt_manager, risks_to_check, model: "gemini-1.5-pro-latest")
39
+ # Updated to accept file_path keyword argument
40
+ def process_in_chunks(content, gemini_client, json_schema, prompt_manager, risks_to_check,
41
+ model: LLMClient::DEFAULT_MODEL, file_path: nil)
32
42
  all_results = []
33
43
  chunks = split(content)
34
44
 
35
- puts "Splitting content into #{chunks.size} chunks..."
45
+ puts "[DEBUG Omamori DiffSplitter] Splitting content into #{chunks.size} chunks for file: #{file_path || 'N/A'}"
36
46
 
37
47
  chunks.each_with_index do |chunk, index|
38
- puts "Processing chunk #{index + 1}/#{chunks.size}..."
39
- prompt = prompt_manager.build_prompt(chunk, risks_to_check, json_schema)
40
- result = gemini_client.analyze(prompt, json_schema, model: model)
48
+ puts "[DEBUG Omamori DiffSplitter] Processing chunk #{index + 1}/#{chunks.size} for file: #{file_path || 'N/A'}"
49
+ # Pass file_path (potentially modified for chunks) to build_prompt
50
+ chunk_file_path_info = if file_path
51
+ chunks.size > 1 ? "#{file_path} (chunk #{index + 1}/#{chunks.size})" : file_path
52
+ end
53
+ prompt = prompt_manager.build_prompt(chunk, risks_to_check, json_schema, file_path: chunk_file_path_info)
54
+ result = gemini_client.analyze(prompt, json_schema, model: model) # This call to analyze is correct
41
55
  all_results << result
42
- # TODO: Handle potential rate limits or errors between chunks
43
56
  end
44
-
45
- # TODO: Combine results from all chunks
46
57
  combine_results(all_results)
47
58
  end
48
59
 
49
60
  private
50
61
 
51
62
  def combine_results(results)
52
- # This is a placeholder. Combining results from multiple AI responses
53
- # requires careful consideration of overlapping findings, context, etc.
54
- # For now, just flatten the list of security risks.
55
63
  combined_risks = results.flat_map do |result|
56
- result && result["security_risks"] ? result["security_risks"] : []
64
+ result && result['security_risks'] ? result['security_risks'] : []
57
65
  end
58
- { "security_risks" => combined_risks }
66
+ { 'security_risks' => combined_risks }
59
67
  end
60
68
  end
61
69
  end
62
- end
70
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omamori
4
+ module AIAnalysisEngine
5
+ # Asks the primary reporter, and the fallback reporter only when the primary gives no answer
6
+ # (a refusal, an error, or an unusable response). Same interface as LLMClient#analyze.
7
+ class FallbackClient
8
+ def initialize(primary, fallback, fallback_model:)
9
+ @primary = primary
10
+ @fallback = fallback
11
+ @fallback_model = fallback_model
12
+ end
13
+
14
+ def analyze(prompt, json_schema, model: nil)
15
+ result = @primary.analyze(prompt, json_schema, model: model)
16
+ return result if result
17
+
18
+ puts 'Asking the fallback reporter...'
19
+ @fallback.analyze(prompt, json_schema, model: @fallback_model)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ruby_llm_context'
4
+
5
+ module Omamori
6
+ module AIAnalysisEngine
7
+ # Asks a decision model (TypeSafe Jev, OpenAI Decisions, ...) yes/no questions through RubyLLM.judge.
8
+ class JudgeClient
9
+ # Input tokens used so far, across threads
10
+ attr_reader :input_tokens
11
+
12
+ # max_questions: split larger question sets into several requests (OpenAI accepts up to 200 per request)
13
+ def initialize(context, provider:, model:, max_questions: nil)
14
+ @context = context
15
+ @provider = provider
16
+ @model = model
17
+ @max_questions = max_questions
18
+ @input_tokens = 0
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ # questions: { name => instructions }
23
+ # Returns { name => probability that the answer is yes, or nil when the model gave none (e.g. a refusal) }
24
+ def classify(state, questions)
25
+ batches = @max_questions ? questions.each_slice(@max_questions).map(&:to_h) : [questions]
26
+ batches.map { |batch| judge(state, batch) }.reduce({}, :merge)
27
+ end
28
+
29
+ private
30
+
31
+ def judge(state, questions)
32
+ judgment = @context.judge(
33
+ state,
34
+ questions: questions.transform_values { |instructions| { type: :probability, instructions: instructions } },
35
+ model: @model, provider: @provider, assume_model_exists: true
36
+ )
37
+ @mutex.synchronize { @input_tokens += judgment.tokens&.input.to_i }
38
+ questions.keys.to_h do |name|
39
+ answer = judgment.answers[name]
40
+ [name, answer.respond_to?(:probability) ? answer.probability : nil]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'ruby_llm_context'
5
+
6
+ module Omamori
7
+ module AIAnalysisEngine
8
+ # Asks any RubyLLM chat model for a structured security report.
9
+ class LLMClient
10
+ DEFAULT_MODEL = 'gemini-3.8-flash'
11
+ EMPTY_RESPONSE_RETRIES = 2
12
+ # Finish reasons that mean the model refused; asking again gives the same result
13
+ REFUSAL_REASONS = %i[other safety prohibited_content blocklist spii recitation refusal content_filter].freeze
14
+
15
+ def initialize(context, provider: nil)
16
+ @context = context
17
+ @provider = provider
18
+ end
19
+
20
+ # Returns the parsed JSON (a Hash with 'security_risks') or nil
21
+ def analyze(prompt, json_schema, model: DEFAULT_MODEL)
22
+ response = ask_with_text(prompt, json_schema, model)
23
+ return nil unless response
24
+
25
+ parsed = JSON.parse(response.content.gsub(/\A```json\n|```\z/, '').strip)
26
+ return parsed if parsed.is_a?(Hash) && parsed.key?('security_risks')
27
+
28
+ puts 'Warning: Unexpected AI analysis response structure.'
29
+ puts "Raw response text: #{response.content}"
30
+ nil
31
+ rescue JSON::ParserError
32
+ puts 'Warning: Failed to parse response text as JSON.'
33
+ puts "Raw response text: #{response.content}"
34
+ nil
35
+ rescue RubyLLM::Error => e
36
+ puts "API Error: #{e.message}"
37
+ nil
38
+ rescue StandardError => e
39
+ puts "An unexpected error occurred during API call: #{e.message}"
40
+ nil
41
+ end
42
+
43
+ private
44
+
45
+ # Models occasionally return an empty response. Retry a couple of times before giving up,
46
+ # unless the model refused: an unchecked chunk must not look like a clean result.
47
+ def ask_with_text(prompt, json_schema, model)
48
+ (EMPTY_RESPONSE_RETRIES + 1).times do
49
+ response = chat(model).with_schema(json_schema).ask(prompt)
50
+ return response unless response.content.to_s.strip.empty?
51
+
52
+ reason = response.finish_reason
53
+ if REFUSAL_REASONS.include?(reason.to_s.downcase.to_sym)
54
+ puts "Warning: The model refused to analyze this content (finish reason: #{reason}). It was NOT checked."
55
+ return nil
56
+ end
57
+
58
+ puts "Warning: Empty AI response (finish reason: #{reason || 'unknown'}). Retrying..."
59
+ end
60
+ puts 'Warning: AI returned no text after retries.'
61
+ nil
62
+ end
63
+
64
+ def chat(model)
65
+ return @context.chat(model: model) unless @provider
66
+
67
+ @context.chat(model: model, provider: @provider, assume_model_exists: true)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ require 'yaml'
2
2
 
3
3
  module Omamori
4
4
  module AIAnalysisEngine
@@ -10,7 +10,9 @@ module Omamori
10
10
  Report any detected risks in the format specified by the following JSON Schema:
11
11
  %{json_schema}
12
12
  If no risks are found, output an empty list for the "security_risks" array.
13
+ If the code is a git diff, a vulnerability can also come from removing or weakening an existing protection, not only from added code.
13
14
  Please provide your response in %{language}.
15
+ #{"File context (if available): %{file_path}" if ENV['OMAMORI_DEBUG_PROMPT']}
14
16
 
15
17
  【Code to Analyze】:
16
18
  %{code_content}
@@ -48,11 +50,13 @@ dangerous_eval_prompt = <<~PROMPT
48
50
  Search for methods enabling dynamic code execution in Ruby code (e.g., eval, instance_eval, class_eval, send, public_send, system, exec, backticks `).
49
51
  Check if arguments passed to these methods originate from or are directly influenced by external untrusted input (e.g., HTTP request parameters params, data from files, network responses). Look for patterns similar to the vulnerable Ruby examples shown above.
50
52
  Verify if user input is rigorously sanitized or validated specifically to prevent code injection vectors before being used in these methods. Standard escaping for HTML (like XSS prevention) is not sufficient here. Check if execution is restricted only to a predefined, absolutely safe allowlist of commands or methods if dynamic execution cannot be avoided.
51
- Assess if safer alternatives exist that can achieve the same functionality without dynamic code execution. Examples include using Hash lookups for dispatching actions, case statements based on input values, leveraging safe templating engines, or using specific library functions designed for the task instead of generic execution methods.
53
+ Assess if safer alternatives exist that can achieve the same functionality without dynamic code execution. Examples include using Hash lookups for dispatching actions, case statements based on input values, leveraging safe templating engines, or using specific library functions designed for the task instead of generic execution methods.
52
54
  PROMPT
53
-
55
+
54
56
  RISK_PROMPTS = {
55
57
  xss: "Cross-Site Scripting (XSS): A vulnerability where user input is not properly escaped and is embedded into HTML or JavaScript, leading to arbitrary script execution in the victim's browser. Detection steps: 1) Identify where user input is output to HTML/JS context. 2) Check if proper encoding/escaping is applied (e.g., html_safe, raw, sanitize, escape_javascript). 3) Look for unsafe methods that bypass default Rails escaping (html_safe, raw, <%==). 4) Examine JavaScript that incorporates user input via template interpolation. 5) Check for improper content-type headers that might enable XSS. 6) Verify if user input is passed to eval(), setTimeout(), document.write() or DOM manipulation functions. 7) Look for attribute injection possibilities where user input sets HTML attributes.",
58
+ sql_injection: "SQL Injection: User input is placed into a SQL query as raw text, so an attacker can change the query to read, modify, or delete data or bypass authentication. Detection steps: 1) Find ActiveRecord methods that accept raw SQL strings (where, having, joins, from, group, select, lock, find_by_sql, order or pluck with Arel.sql) and direct connection calls (execute, select_all, select_value, exec_query). 2) Check whether user input (params, request data, cookies, or values derived from them) is interpolated or concatenated into those strings, including hand-built LIKE and IN (...) clauses. 3) Treat hash conditions, ? or named placeholders, sanitize_sql_array, connection.quote, and integer casting as safe. 4) Remember that since Rails 6, order and pluck reject raw SQL that is not a column name, unless it is wrapped in Arel.sql. 5) Check column or direction names taken from user input are mapped through an allowlist.",
59
+ mass_assignment: "Mass Assignment: A model is created or updated from request parameters without restricting which attributes may be set, so a user can change fields they should not control, such as their role, owner, price, or balance. Detection steps: 1) Find model writes (new, create, update, assign_attributes, insert_all, upsert) that take request data. 2) Check for params.permit!, to_unsafe_h / to_unsafe_hash, and permit lists that are not fixed (permit(*Model.column_names), a list chosen by the client). A hash permit such as permit(preferences: {}) is only a problem when its keys become model attributes, not when it fills a single JSON or serialized column. 3) Check for attributes that bypass strong parameters, such as JSON.parse(request.body.read) or request.request_parameters passed to a model. 4) Check whether user-facing actions (sign-up, profile, member APIs) permit sensitive attributes: role, admin, owner_id / user_id / account_id, balance, price, plan, verified, confirmed_at. 5) Treat an explicit permit list without sensitive attributes, params.expect with such a list, and sensitive attributes permitted only in admin-only actions with an authorization check as safe.",
56
60
  csrf: "Cross-Site Request Forgery (CSRF): An attack that forces an authenticated user to perform unwanted actions via forged requests. Detection steps: 1) Check if CSRF protection is disabled globally or for specific controllers/actions (skip_before_action :verify_authenticity_token). 2) Look for APIs or endpoints that handle state-changing operations (POST, PUT, DELETE methods). 3) Verify if authenticity tokens are properly validated for forms and AJAX requests. 4) Check if the application relies solely on cookies for authentication without additional CSRF protection. 5) Look for custom CSRF protection implementations that might be incomplete. 6) Verify if SameSite cookie attributes are properly set. 7) Check if the application validates the Origin or Referer header for cross-origin requests.",
57
61
  idor: "Insecure Direct Object Reference (IDOR): Occurs when object identifiers (e.g., IDs) are exposed and access control is missing, allowing unauthorized access to other users' data. Detection steps: 1) Identify endpoints that access data using user-supplied identifiers (e.g., params[:id]). 2) Check if proper authorization checks exist before accessing the data (e.g., current_user.orders vs Order.find). 3) Look for functions retrieving data without verifying the current user's ownership or access rights. 4) Check for sequential or predictable IDs that can be enumerated. 5) Verify if sensitive operations verify resource ownership before modifications. 6) Look for authorization checks that can be bypassed through parameter manipulation. 7) Examine APIs that return data based on user-supplied identifiers.",
58
62
  open_redirect: "Open Redirect: Redirecting users to external URLs based on user-supplied input without proper validation. Detection steps: 1) Identify redirect methods or functions (redirect_to, headers['Location'], response.redirect, etc.). 2) Check if the redirect URL or destination can be controlled by user input. 3) Verify if there is proper validation of the redirect URL to prevent external redirects. 4) Look for validation patterns that only check for URL prefixes that could be bypassed. 5) Check if the code restricts redirects to only allowed domains or uses relative paths. 6) Watch for URL manipulation techniques that might bypass validation (using //, additional domains in path, URL encoding).",
@@ -65,30 +69,28 @@ dangerous_eval_prompt = <<~PROMPT
65
69
  dangerous_eval: "Dangerous Code Execution (eval, exec): Dynamic code execution using untrusted input, allowing arbitrary code injection. Detection steps: 1) Search for methods enabling dynamic code execution (eval, instance_eval, class_eval, send, public_send, system, exec, ` ``). 2) Check if arguments passed to these methods originate from or are influenced by user input. 3) Verify if user input is rigorously sanitized or validated before use, or if execution is restricted to safe, predefined commands/methods. 4) Assess if safer alternatives can replace dynamic execution.",
66
70
  inappropriate_file_permissions: "Insecure File Permissions: Files or directories with overly permissive modes (e.g., 777), allowing unauthorized read/write/execute access. Detection steps: 1) Find code that creates files/directories or changes permissions (e.g., FileUtils.chmod, File.chmod, Dir.mkdir with mode). 2) Examine the permission modes being set (e.g., 0777, 0666). 3) Evaluate if permissions follow the principle of least privilege, especially avoiding world-writable (o+w) or world-readable (o+r) for sensitive files. 4) Check permissions of configuration files, log files, and uploaded files.",
67
71
  temporary_backup_file_leak: "Temporary or Backup File Exposure: Sensitive files like .bak, .tmp, or ~ versions are publicly accessible due to poor file handling. Detection steps: 1) Identify logic creating temporary or backup files. 2) Check if common backup extensions (.bak, .tmp, ~, .old) are used. 3) Verify these files are not created within web-accessible directories. 4) Ensure temporary/backup files are securely deleted after use. 5) Check if .gitignore excludes these file patterns.",
68
- overly_detailed_errors: "Excessive Error Information Disclosure: Stack traces or internal error messages exposed to users, leaking implementation details. Detection steps: 1) Examine error handling code (rescue blocks, error page rendering). 2) Check error messages displayed to users in the production environment. 3) Verify that stack traces, internal paths, database queries, or configuration details are not included in user-facing errors. 4) Confirm framework settings disable detailed errors in production (e.g., Rails `config.consider_all_requests_local = false`). 5) Ensure generic errors are shown to users, while details are logged server-side.",
69
- csp_not_set: "Missing Content Security Policy (CSP): Absence of CSP headers increases risk of XSS. Look for missing Content-Security-Policy header. Detection steps: 1) Identify code setting HTTP response headers (middleware, controllers). 2) Check for the presence of the `Content-Security-Policy` header. 3) If missing or policy is overly permissive (e.g., includes 'unsafe-inline', 'unsafe-eval', '*'), report as risk. 4) Check framework CSP configuration mechanisms (e.g., Rails `config.content_security_policy`).",
70
- mime_sniffing_vulnerability: "MIME Sniffing Vulnerability: Missing X-Content-Type-Options: nosniff header can allow browsers to misinterpret content types. Detection steps: 1) Identify code setting HTTP response headers. 2) Check for the presence of the `X-Content-Type-Options` header. 3) If the header is missing or its value is not exactly `nosniff`, report as risk. 4) Check framework defaults or security middleware for automatic inclusion.",
71
- clickjacking_vulnerability: "Clickjacking Protection Missing: Absence of X-Frame-Options or frame-ancestors directive allows malicious framing of pages. Detection steps: 1) Identify code setting HTTP response headers. 2) Check for the `X-Frame-Options` header (e.g., `DENY`, `SAMEORIGIN`). 3) Alternatively, check for the `frame-ancestors` directive in the `Content-Security-Policy` header (e.g., `'none'`, `'self'`). 4) If neither is present or configured securely, report as risk. 5) Check framework defaults or security middleware.",
72
- auto_index_exposure: "Auto Indexing Enabled: Directory listing is active, exposing files and internal structure to users. Detection steps: 1) Review web server configuration files (Nginx, Apache). 2) Look for directives enabling directory listing (e.g., Apache `Options +Indexes`, Nginx `autoindex on`). 3) Check if directory listing is unintentionally enabled for specific locations. 4) If the application framework serves static files, ensure its directory listing feature is disabled.",
73
- inappropriate_password_policy: "Weak Password Policy: Inadequate rules such as short length, lack of complexity, or missing brute-force protections. Detection steps: 1) Locate code related to password setting/changing. 2) Check validation logic for minimum length, complexity requirements (character types). 3) Verify if checks against common weak passwords (dictionaries, patterns) exist. 4) Look for enforcement of password history (reuse prevention) and expiration. 5) Check for related brute-force protection (see `excessive_login_attempts`).",
74
- two_factor_auth_missing: "Missing Two-Factor Authentication (2FA): Lack of secondary authentication factor for sensitive operations. Detection steps: 1) Identify login process and code for sensitive operations (e.g., profile change, payment). 2) Check if a second factor (SMS, TOTP, key) is required beyond the password. 3) Look for 2FA setup and management features. 4) Determine if 2FA is mandatory or optional for users.",
72
+ overly_detailed_errors: "Excessive Error and Server Information Disclosure: Stack traces, internal error messages, or server details (software names and versions, hostnames, internal paths, database queries) are exposed to users, leaking implementation details. Detection steps: 1) Examine error handling code (rescue blocks, rescue_from, error page rendering, exceptions_app). 2) Check what error responses send to the client in production, including API JSON errors. 3) Flag responses that include exception messages, backtraces, internal paths, queries, configuration, or server and framework versions. 4) Flag production settings that show detailed errors (config.consider_all_requests_local = true) or headers that reveal server software and versions. 5) Treat generic messages shown to users, with details only logged on the server (for example with a request id), as safe.",
73
+ csp_not_set: "Weakened or Removed Content Security Policy: The change removes, disables, or loosens the Content-Security-Policy, which makes XSS easier to exploit. Detection steps: 1) Look for changes to the CSP configuration (config/initializers/content_security_policy.rb, content_security_policy blocks or calls in controllers, headers set by hand or in a web server config). 2) Flag removing the policy, turning it off (content_security_policy false), switching it to report-only in production, removing nonces, or adding 'unsafe-inline', 'unsafe-eval', wildcards (*), or data: to script-src or default-src. 3) Adding one specific trusted host while keeping nonces and the rest of the policy is not a weakening. 4) Do not flag code that does not touch the policy: whether the application has a CSP at all cannot be judged from a change that does not touch it.",
74
+ mime_sniffing_vulnerability: "Weakened MIME Sniffing Protection: The change removes or overrides X-Content-Type-Options: nosniff, or starts serving user-supplied files with a content type the user controls, so browsers may run uploaded content as HTML or script. Detection steps: 1) Look for changes to default headers (config.action_dispatch.default_headers), headers set or deleted in controllers or middleware, and web server header settings. 2) Flag replacing the default headers without nosniff, deleting the header, or serving uploads inline with the uploaded content type. 3) Adding other headers while keeping nosniff is not a weakening. 4) Do not flag code that does not touch these headers or file serving.",
75
+ clickjacking_vulnerability: "Weakened Clickjacking Protection: The change removes or loosens X-Frame-Options or the CSP frame-ancestors directive, so other sites can frame the application's pages. Detection steps: 1) Look for changes to default headers, headers set or deleted in controllers, CSP frame_ancestors, and web server header settings. 2) Flag deleting X-Frame-Options without an equivalent frame-ancestors, setting ALLOWALL or frame-ancestors *, or allowing any origin to frame sensitive pages. 3) Allowing framing only by specific trusted origins, or relaxing DENY to SAMEORIGIN, is not a weakening. 4) Do not flag code that does not touch framing protection.",
76
+ auto_index_exposure: "Directory Listing Enabled: The change turns on automatic directory listings, exposing files and the internal structure. Detection steps: 1) Look for web server configuration (nginx autoindex on, Apache Options +Indexes) and Rack or Rails code that serves directory listings (Rack::Directory, a file browser over a public path). 2) Flag enabling listings for a location that holds uploads, exports, backups, or other non-public files. 3) Turning listings off (autoindex off, Options -Indexes) or serving files through an internal location is not a problem.",
77
+ inappropriate_password_policy: "Weakened Password Policy: The change makes password requirements weaker, for example a shorter minimum length, removing complexity or breached-password checks, lowering the hashing cost, or allowing a password change without the current password. Detection steps: 1) Look for changes to password validations, Devise configuration (password_length, stretches), and password change actions. 2) Flag any requirement that becomes weaker than before. 3) Making requirements stronger, or refactoring them without weakening, is not a problem. 4) Do not flag code that does not touch passwords.",
78
+ two_factor_auth_missing: "Bypassed Two-Factor Authentication: The change removes or bypasses an existing second-factor step, or adds a sensitive action (sign-in, payouts, account or security settings, admin operations) that skips the two-factor check the other sensitive actions have. Detection steps: 1) Look for changes to sign-in flows and to before_action filters that require a second factor. 2) Flag removing the second-factor step, excluding a sensitive action from the filter, or letting users in before the second factor is verified. 3) Refactoring that keeps the second-factor step is not a problem. 4) Do not flag code that has nothing to do with authentication or sensitive actions: whether the application offers two-factor authentication at all cannot be judged from such a change.",
75
79
  race_condition: "Race Condition: Concurrent access without proper locking can lead to inconsistent states or privilege escalation. Detection steps: 1) Identify code accessing shared resources (database records, files, cache). 2) Find sections where multiple threads/processes might read/write concurrently. 3) Look for non-atomic check-then-act patterns. 4) Verify use of proper locking mechanisms (database transactions, Mutex, file locks). 5) Focus on critical operations like balance updates, inventory control.",
76
- server_error_information_exposure: "Server Error Information Exposure: Internal errors (e.g., 500) reveal stack traces or server information in responses. Detection steps: 1) Examine error handling for server-side errors (e.g., HTTP 500). 2) Check the content of error responses sent to the client in production. 3) Verify that server-specific information (software type/version, OS details) is not included. 4) Check server configuration to suppress revealing headers (e.g., `Server` header; Nginx `server_tokens off;`). 5) Ensure detailed errors are logged only, with generic messages shown to users. (Similar to `overly_detailed_errors` but focusing on server info).",
77
- dependency_trojan_package: "Dependency Trojan Package Risk: Installation of malicious or typosquatted packages from untrusted sources. Detection steps: 1) Review dependency files (`Gemfile`, `Gemfile.lock`). 2) Scan the list of Gems for typosquatting or unfamiliar names. 3) Check Gem sources (avoid untrusted Git repos if possible). 4) Investigate reputation and maintenance status of less-known Gems. 5) Ensure `Gemfile.lock` is committed to version control. 6) Check if dependency scanning tools (e.g., `bundler-audit`) are used.",
78
80
  api_overexposure: "Excessive API Exposure: Public APIs exposed without authentication, leading to data leakage or unauthorized access. Detection steps: 1) Identify all API endpoint definitions. 2) Verify that appropriate authentication and authorization checks are applied to each endpoint. 3) Check if rate limiting or access controls are in place, even for public APIs. 4) Ensure sensitive data or internal-only information is not returned by unauthenticated endpoints. 5) Review API documentation for unintended exposure.",
79
81
  security_middleware_disabled: "Security Middleware Disabled: Important protections (e.g., CSRF tokens, input sanitization) are turned off or removed. Detection steps: 1) Review framework initialization and configuration files. 2) Look for intentional disabling or removal of standard security middleware (e.g., CSRF protection, secure session handling). 3) Check for route-specific or controller-specific disabling of security features (e.g., `skip_before_action :verify_authenticity_token`) and evaluate the justification.",
80
- security_header_inconsistency: "Security Header Inconsistency: Inconsistent or missing security headers across environments or routes. Detection steps: 1) Locate code setting security headers (CSP, X-Frame-Options, HSTS, etc.). 2) Compare header settings across different environments (dev, staging, prod), ensuring production is not weaker. 3) Verify consistent application of headers across different routes/pages within the application. 4) Check for conflicts if headers are set in multiple places (middleware, reverse proxy).",
82
+ security_header_inconsistency: "Inconsistent Security Headers: The change makes security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) weaker in production than elsewhere, or removes them for particular controllers or routes. Detection steps: 1) Look for header settings that differ by environment, and for controllers or routes that delete or override headers. 2) Flag production being weaker than other environments, or a group of pages (for example billing or embeds) losing headers the rest of the application has. 3) Applying the same headers to every environment and route is not a problem. 4) Do not flag code that does not touch security headers.",
81
83
  excessive_login_attempts: "Excessive Login Attempts Allowed: Lack of rate limiting allows brute-force login attempts. Detection steps: 1) Identify the login authentication code. 2) Check for logic that counts and limits login attempts per account and/or IP address within a time window. 3) Verify that countermeasures (account lockout, CAPTCHA, delay) are triggered upon exceeding the limit. 4) Check configuration of rate-limiting libraries (e.g., `rack-attack`). 5) Ensure similar limits exist for password reset functions.",
82
84
  inappropriate_cache_settings: "Insecure Cache Settings: Sensitive pages are cached publicly (e.g., with Cache-Control: public), risking data leakage. Detection steps: 1) Identify code setting `Cache-Control` or `Pragma` headers. 2) Locate code generating pages containing user-specific or sensitive data. 3) Ensure these pages have restrictive cache directives (`private`, `no-store`, `no-cache`). 4) Look for inappropriate use of `public` or long `max-age`. 5) Review framework caching (page, action, fragment) usage for sensitive content. 6) Consider cache settings in reverse proxies/CDNs.",
83
85
  secret_key_committed: "Secret Key Committed to Repository: Credentials, JWT secrets, or API keys are hardcoded or pushed to version control. Detection steps: 1) Scan codebase for hardcoded secrets (passwords, API keys, tokens). 2) Review configuration files (`database.yml`, `secrets.yml`, `.env`) for secrets and ensure they are gitignored if present. 3) Verify secrets are loaded from environment variables or dedicated secret management systems. 4) Scan version control history for accidentally committed secrets (and rotate if found). 5) Consider using secret scanning tools (e.g., `truffleHog`, `gitleaks`).",
84
86
  third_party_script_validation_missing: 'Missing Validation for Third-Party Scripts: External scripts are loaded without integrity checks (e.g., Subresource Integrity). Detection steps: 1) Find HTML templates loading external JS/CSS (`<script src="...">`, `<link href="...">`). 2) Check if these tags include the `integrity` attribute (for SRI). 3) Report as risk if `integrity` attribute is missing or empty. 4) Verify the `crossorigin="anonymous"` attribute is also present when using SRI. 5) Check dynamically loaded scripts for similar integrity validation mechanisms.',
85
87
  over_logging: "Over-Logging: Logging sensitive information such as passwords, tokens, or personal data. Detection steps: 1) Identify all logging statements (`Rails.logger.*`, `puts`, etc.). 2) Examine the data being logged. 3) Check specifically for sensitive data like passwords, tokens, API keys, PII, credit card numbers. 4) Pay attention to logging of entire request parameters or exception objects. 5) Verify framework parameter filtering (e.g., Rails `config.filter_parameters`) is configured correctly to mask sensitive fields.",
86
88
  fail_open_design: "Fail-Open Design: On error or exception, access is granted instead of safely denied. Detection steps: 1) Identify security check code (authentication, authorization, access control). 2) Examine behavior within exception handling blocks (`rescue`). 3) Verify that exceptions during security checks default to denying access (fail-close/fail-safe), not granting it. 4) Look for checks where an error state might be misinterpreted as permissive. 5) Ensure critical operations use transactions and roll back safely on error.",
87
- environment_differences: "Uncontrolled Environment Differences: Security settings differ between development and production without strict controls. Detection steps: 1) Compare configuration files across environments (`environments/development.rb` vs `environments/production.rb`). 2) Identify differences in security settings (error reporting, SSL, headers, auth). 3) Evaluate if differences weaken security in production. 4) Check for a process to manage and review environment-specific configurations. 5) Consider potential infrastructure differences (firewalls, server settings).",
88
- audit_log_missing: "Missing Audit Logging: Lack of logging for critical actions or authorization checks prevents accountability. Detection steps: 1) Identify code performing critical actions (login, permission change, sensitive data access/modification, config change). 2) Verify these actions generate logs including who (user), when (timestamp), what (action/resource), result (success/fail), and where (IP address). 3) Check if authentication successes/failures and authorization failures are logged. 4) Assess if logs are stored securely and retained appropriately. 5) Ensure log format is consistent and useful for monitoring.",
89
+ environment_differences: "Weakened Production Configuration: The change loosens security settings in the production environment, such as turning off force_ssl or assume_ssl, clearing allowed hosts, enabling detailed error pages (consider_all_requests_local = true), enabling debug logging of request data, or disabling protections that the other environments keep. Detection steps: 1) Look for changes to config/environments/production.rb and other production-only configuration. 2) Flag settings in production that become weaker than before. 3) Loosening settings only in development or test is not a problem. 4) Adding unrelated production settings (mailer, queue, cache) while keeping the security settings is not a problem.",
90
+ audit_log_missing: "Removed Audit Logging: The change removes audit logging from security-relevant actions (sign-in, permission or role changes, API key creation or revocation, access to sensitive data), or adds such an action without the audit record that similar actions in the same code write. Detection steps: 1) Look for audit or security event records (AuditLog, SecurityEvent, PaperTrail, logging calls) in the changed actions and their neighbours. 2) Flag records that are removed, or a new sensitive action that does not record what its sibling actions record. 3) Moving the logging to another place (a service object, a callback, the controller) while keeping it is not a problem. 4) Do not flag code that has nothing to do with security-relevant actions.",
89
91
  time_based_side_channel: "Time-Based Side Channel: Execution time differences can leak secrets (e.g., timing attacks in string comparison). Detection steps: 1) Locate code comparing secret values (passwords, tokens, API keys). 2) Check if standard comparison operators (`==`) are used for secrets. 3) Verify use of constant-time comparison functions (e.g., `ActiveSupport::SecurityUtils.secure_compare`, `Rack::Utils.secure_compare`). 4) Analyze cryptographic operations for potential timing leaks (may depend on library implementation). 5) Consider if database query times varying based on input could leak information."
90
92
  }.freeze
91
-
93
+
92
94
 
93
95
  def initialize(config = {})
94
96
  # Load custom templates and language from config, merge with default
@@ -98,12 +100,87 @@ dangerous_eval_prompt = <<~PROMPT
98
100
  @language = config.get("language", "en") # Get language from config, default to 'en'
99
101
  end
100
102
 
101
- def build_prompt(code_content, risks_to_check, json_schema, template_key: :default)
102
- # Use the template from @prompt_templates, defaulting to :default if template_key is not found
103
+ # Builds a yes/no question for decision models (Jev, OpenAI Decisions).
104
+ # The full risk description is included so the classifier sees the same detection steps as the LLM.
105
+ def build_question(risk_key)
106
+ <<~TEXT
107
+ Does the following code contain the security vulnerability described below? #{DIFF_NOTE}
108
+
109
+ #{@risk_prompts.fetch(risk_key)}
110
+ TEXT
111
+ end
112
+
113
+ # Real vulnerabilities often come from removing a protection, so removed lines matter too
114
+ # Former risk names that now belong to another risk
115
+ RISK_ALIASES = { server_error_information_exposure: :overly_detailed_errors }.freeze
116
+ # Former risks that are better left to other tools, with what to use instead
117
+ RETIRED_RISKS = {
118
+ dependency_trojan_package: 'Malicious or typosquatted packages cannot be judged reliably from code. ' \
119
+ 'Use bundler-audit (run by omamori) for known vulnerable versions, and a ' \
120
+ 'malicious-package scanner such as OSV-Scanner or GitHub dependency review.'
121
+ }.freeze
122
+
123
+ DIFF_NOTE = 'The code may be a git diff, where lines starting with + are added and lines starting with - are ' \
124
+ 'removed. A change can introduce the vulnerability by adding unsafe code, or by removing or ' \
125
+ 'weakening a protection that was there before (a check, an escape, a filter or allowlist, an ' \
126
+ 'authorization or authentication step, or a security setting). Judge the code as it is after the change.'
127
+ SUB_CHECKS_DIR = File.expand_path('sub_checks', __dir__)
128
+ SUB_CHECK_SEPARATOR = '__'
129
+
130
+ # The overall question for a risk plus one question per sub-check (a concrete pattern that alone makes the
131
+ # risk real). Returns { question_name => text }; sub-check names look like :xss__raw_output.
132
+ def build_questions(risk_key, sub_checks: true)
133
+ questions = { risk_key => build_question(risk_key) }
134
+ return questions unless sub_checks
135
+
136
+ sub_checks_for(risk_key).each do |name, text|
137
+ questions[:"#{risk_key}#{SUB_CHECK_SEPARATOR}#{name}"] = "#{text} #{DIFF_NOTE}"
138
+ end
139
+ questions
140
+ end
141
+
142
+ # The risk a question name belongs to (:xss__raw_output -> :xss)
143
+ def self.risk_of(question_name)
144
+ question_name.to_s.split(SUB_CHECK_SEPARATOR, 2).first.to_sym
145
+ end
146
+
147
+ def sub_check_text(question_name)
148
+ risk, name = question_name.to_s.split(SUB_CHECK_SEPARATOR, 2)
149
+ name && sub_checks_for(risk.to_sym)[name]
150
+ end
151
+
152
+ def sub_checks_for(risk_key)
153
+ @sub_checks ||= {}
154
+ @sub_checks[risk_key] ||= begin
155
+ path = File.join(SUB_CHECKS_DIR, "#{risk_key}.yml")
156
+ File.exist?(path) ? YAML.safe_load_file(path) : {}
157
+ end
158
+ end
159
+
160
+ # Updated to accept file_path keyword argument
161
+ def build_prompt(code_content, risks_to_check, json_schema, template_key: :default, file_path: nil)
103
162
  template = @prompt_templates.fetch(template_key, @prompt_templates[:default])
104
163
  risk_list = risks_to_check.map { |risk_key| @risk_prompts[risk_key] }.compact.join(", ")
105
164
 
106
- template % { risk_list: risk_list, code_content: code_content, json_schema: json_schema.to_json, language: @language}
165
+ prompt_variables = {
166
+ risk_list: risk_list,
167
+ code_content: code_content,
168
+ json_schema: json_schema.to_json,
169
+ language: @language
170
+ }
171
+ # Add file_path to variables if provided and template expects it
172
+ # Ensure your template string (DEFAULT_PROMPT_TEMPLATE or custom ones)
173
+ # actually uses %{file_path} if you want to include it.
174
+ prompt_variables[:file_path] = file_path if file_path && template.include?("%{file_path}")
175
+
176
+ # Handle cases where a key in prompt_variables might not be in the template string
177
+ # by selecting only keys present in the template.
178
+ final_variables = {}
179
+ template.scan(/%\{(\w+)\}/).flatten.uniq.each do |key_in_template|
180
+ final_variables[key_in_template.to_sym] = prompt_variables[key_in_template.to_sym]
181
+ end
182
+
183
+ template % final_variables
107
184
  end
108
185
  end
109
186
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_llm'
4
+
5
+ module Omamori
6
+ module AIAnalysisEngine
7
+ # Builds a RubyLLM context whose credentials come from each provider's usual environment variable
8
+ # (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, TYPESAFE_API_KEY, ...), overridden by `overrides`.
9
+ module RubyLLMContext
10
+ def self.build(overrides = {})
11
+ RubyLLM.context do |config|
12
+ config.public_methods.grep(/_api_(?:key|base)=\z/).each do |setter|
13
+ value = ENV.fetch(setter.to_s.delete_suffix('=').upcase, nil)
14
+ config.public_send(setter, value) if value
15
+ end
16
+ overrides.compact.each do |name, value|
17
+ config.public_send("#{name}=", value) if config.respond_to?("#{name}=")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # Concrete patterns that are each enough to make missing audit logging real on their own.
2
+ # Each question asks for the pattern AND a missing defense, so that code which uses the same construct safely is a no.
3
+ record_removed: >-
4
+ Does the change delete an audit or security event record (AuditLog, SecurityEvent, PaperTrail, or a logging call
5
+ that records who did what) from an action such as sign-in, a role or permission change, API key creation or
6
+ revocation, or access to sensitive data? Answer no when the same record is still written somewhere else, for
7
+ example moved into a service object, a callback, or the controller.
8
+ new_action_without_record: >-
9
+ Does the change add a security-relevant action (a role or permission change, granting or revoking access, creating
10
+ or revoking credentials, exporting or deleting data) next to existing actions that write an audit or security event
11
+ record, while the new action writes none? Answer no when the new action records the same kind of event as its
12
+ neighbours.
@@ -0,0 +1,22 @@
1
+ # Concrete patterns that are each enough to make CSRF real on their own.
2
+ # Each question asks for the pattern AND a missing defense, so that code which uses the same construct safely is a no.
3
+ state_change_on_get: >-
4
+ Does the code make an action that changes data or account state (create, update, delete, cancel, sign out,
5
+ turn a setting on or off, send something) reachable with a GET request, through a get route, match ... via: :all,
6
+ or a link that triggers it without a form? Answer no when such actions are only routed to POST, PATCH, PUT, or
7
+ DELETE and triggered with a form or button_to.
8
+ token_check_skipped: >-
9
+ Does a controller that authenticates users with the session cookie skip or disable the authenticity token check
10
+ for actions that change data (skip_before_action :verify_authenticity_token, skip_forgery_protection,
11
+ protect_from_forgery with except: or an if: condition such as JSON format, or allow_forgery_protection = false
12
+ outside the test environment)? Answer no when the skipped actions do not use cookies to authenticate (for
13
+ example ActionController::API with a bearer token, or a webhook that verifies a signature), or when the setting
14
+ is only for the test environment.
15
+ weak_origin_check: >-
16
+ Does the code replace or relax the token check with a hand-written Origin or Referer check that matches by
17
+ substring or prefix (include?, start_with?, a regular expression without anchors)? Answer no when the token check
18
+ is still in place, or the parsed origin host is compared exactly against a fixed list.
19
+ cross_site_cookie: >-
20
+ Does the code make the session cookie be sent on cross-site requests (same_site: :none) while state-changing
21
+ actions that rely on that session skip the token check? Answer no when those actions still verify the
22
+ authenticity token.
@@ -0,0 +1,9 @@
1
+ # Concrete patterns that are each enough to make insecure deserialization real on their own.
2
+ # Each question asks for the pattern AND a missing defense, so that code which uses the same construct safely is a no.
3
+ object_creating_loader: >-
4
+ Does the code load data that may come from outside the application (request parameters or body, cookies, uploaded
5
+ files, data from another service, or cache or database values users can influence) with a loader that can create
6
+ arbitrary Ruby objects: Marshal.load, YAML.unsafe_load, Psych.unsafe_load, YAML.load on Psych versions before 4,
7
+ Oj.load in object mode or with its default mode, or JSON.load with object creation enabled? Answer no when it uses
8
+ JSON.parse, YAML.safe_load with a short list of permitted classes, Oj in strict or compat mode, or when the data is
9
+ produced and stored only by the application itself.