Module: DebugMcp::CodeSafetyAnalyzer

Defined in:
lib/debug_mcp/code_safety_analyzer.rb

Constant Summary collapse

DANGEROUS_PATTERNS =
{
  file_operations: [
    [/\bFile\s*\.\s*(write|delete|unlink|rename|chmod|chown)\b/, "File.write/delete/unlink/rename"],
    [/\bFileUtils\b/, "FileUtils"],
    [/\bIO\s*\.\s*(write|binwrite)\b/, "IO.write"],
    [/\bDir\s*\.\s*(mkdir|rmdir|delete|unlink)\b/, "Dir.mkdir/rmdir"],
  ],
  system_commands: [
    [/\bsystem\s*\(/, "system()"],
    [/\bexec\s*\(/, "exec()"],
    [/\bspawn\s*\(/, "spawn()"],
    [/`[^`]+`/, "backtick command"],
    [/%x\{/, "%x{}"],
    [/%x\[/, "%x[]"],
    [/%x\(/, "%x()"],
    [/\bOpen3\b/, "Open3"],
    [/\bIO\s*\.\s*popen\b/, "IO.popen"],
  ],
  process_manipulation: [
    [/\bProcess\s*\.\s*(kill|fork|exit)\b/, "Process.kill/fork/exit"],
    [/\bfork\s*[\s({]/, "fork"],
    [/\bexit!/, "exit!"],
    [/\babort\b/, "abort"],
  ],
  network_operations: [
    [/\bNet::HTTP\b/, "Net::HTTP"],
    [/\bTCPSocket\b/, "TCPSocket"],
    [/\bUDPSocket\b/, "UDPSocket"],
    [/\bFaraday\b/, "Faraday"],
    [/\bHTTParty\b/, "HTTParty"],
    [/\bopen-uri\b/, "open-uri"],
    [/\bURI\s*\.\s*open\b/, "URI.open"],
    [/\bRestClient\b/, "RestClient"],
  ],
  destructive_data: [
    [/\.destroy_all\b/, ".destroy_all"],
    [/\.delete_all\b/, ".delete_all"],
    [/\.update_all\b/, ".update_all"],
    [/\b(DROP|TRUNCATE)\s+(TABLE|DATABASE)\b/i, "DROP/TRUNCATE SQL"],
  ],
  mutation_operations: [
    [/\.save!/, ".save!"],
    [/\.save\b(?![!?])/, ".save"],
    [/\.update![\s(]/, ".update!"],
    [/\.update[\s(]/, ".update"],
    [/\.create![\s(]/, ".create!"],
    [/\.create[\s(]/, ".create"],
    [/\.destroy!/, ".destroy!"],
    [/\.destroy\b(?![_!])/, ".destroy"],
    [/\.touch\b/, ".touch"],
    [/\.increment!/, ".increment!"],
    [/\.decrement!/, ".decrement!"],
    [/\.toggle!/, ".toggle!"],
  ],
}.freeze
CATEGORY_LABELS =
{
  file_operations: "File system operations",
  system_commands: "System command execution",
  process_manipulation: "Process manipulation",
  network_operations: "Network operations",
  destructive_data: "Destructive data operations",
  mutation_operations: "Data mutation (modifies database records)",
}.freeze

Class Method Summary collapse

Class Method Details

.analyze(code) ⇒ Object

Analyze code for dangerous patterns. Returns an array of warnings: [{ category:, label:, matches: }]



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/debug_mcp/code_safety_analyzer.rb', line 70

def self.analyze(code)
  warnings = []

  DANGEROUS_PATTERNS.each do |category, patterns|
    matches = []
    patterns.each do |regexp, label|
      matches << label if code.match?(regexp)
    end
    next if matches.empty?

    warnings << { category: category, matches: matches }
  end

  warnings
end

.filter_acknowledged(warnings, acknowledged_categories) ⇒ Object

Filter out warnings whose categories have been acknowledged.



62
63
64
65
66
# File 'lib/debug_mcp/code_safety_analyzer.rb', line 62

def self.filter_acknowledged(warnings, acknowledged_categories)
  return warnings if acknowledged_categories.nil? || acknowledged_categories.empty?

  warnings.reject { |w| acknowledged_categories.include?(w[:category]) }
end

.format_warnings(warnings) ⇒ Object

Format warnings into human-readable text. Returns nil if no warnings. Uses a compact “Note:” format when only mutation_operations are present, and a verbose “WARNING:” format when other categories are involved.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/debug_mcp/code_safety_analyzer.rb', line 99

def self.format_warnings(warnings)
  return nil if warnings.empty?

  # Compact format for mutation-only warnings
  if warnings.all? { |w| w[:category] == :mutation_operations }
    matches = warnings.flat_map { |w| w[:matches] }
    return "Note: Data mutation detected (#{matches.join(", ")}). " \
           "Use acknowledge_mutations to suppress this notice."
  end

  # Verbose format for dangerous operations
  lines = []
  lines << "WARNING: Potentially dangerous operations detected in code."
  lines << "evaluate_code should only be used for investigating runtime state."
  lines << "Use the agent's own tools for file/system/network operations."
  lines << ""

  warnings.each do |w|
    label = CATEGORY_LABELS[w[:category]] || w[:category].to_s
    lines << "  #{label}: #{w[:matches].join(", ")}"
  end

  lines.join("\n")
end