Class: Clacky::Tools::Security::Replacer

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/tools/security.rb

Overview

Internal class that owns per-project state (trash dir, log dir, …). Extracted almost verbatim from the old SafeShell::CommandSafetyReplacer.

Constant Summary collapse

SECRET_WRITE_PATTERNS =

Block writes that would clobber credentials / secrets. These are the only paths truly dangerous to write to by accident:

- ~/.ssh/*          (SSH private keys)
- ~/.aws/*          (AWS credentials)
- any *.env file    (API keys, DB URLs, etc.)

Paths in / outside the project root, Gemfile, README, package.json, etc. are all allowed — the agent is expected to edit them normally.

[
  %r{(?:\A|/)\.ssh/},
  %r{(?:\A|/)\.aws/},
  /(?:\A|\/)\.env(?:\.|\z)/,
  /\.env\z/
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(project_root) ⇒ Replacer

Returns a new instance of Replacer.



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/clacky/tools/security.rb', line 102

def initialize(project_root)
  @project_root = File.expand_path(project_root)

  trash_directory = Clacky::TrashDirectory.new(@project_root)
  @backup_dir = trash_directory.backup_dir

  @project_hash = trash_directory.generate_project_hash(@project_root)
  @safety_log_dir = File.join(Dir.home, ".clacky", "safety_logs", @project_hash)
  FileUtils.mkdir_p(@safety_log_dir) unless Dir.exist?(@safety_log_dir)
  @safety_log_file = File.join(@safety_log_dir, "safety.log")
end

Instance Method Details

#make_command_safe(command) ⇒ Object



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
# File 'lib/clacky/tools/security.rb', line 114

def make_command_safe(command)
  command = command.strip

  # Use a UTF-8-scrubbed copy ONLY for regex checks.  The original
  # bytes are returned unchanged so the shell receives exact paths
  # (e.g. GBK-encoded Chinese filenames in zip archives).
  @safe_check_command = Clacky::Utils::Encoding.safe_check(command)

  case @safe_check_command
  # Block attempts to terminate the clacky server process.
  # IMPORTANT: each verb is anchored with \b so substrings like
  # "Skill" (contains "kill") or "Bill Killalina" don't trigger
  # false positives. We also require `clacky` to appear as a whole
  # word AND within a reasonable distance (same logical command,
  # not hundreds of chars later in an unrelated echo string).
  when /\bpkill\b[^\n;|&]{0,80}\bclacky\b|\bkillall\b[^\n;|&]{0,80}\bclacky\b|\bkill\s+(?:-\S+\s+)*[^\n;|&]{0,40}\bclacky\b/i
    raise SecurityError, "Killing the clacky server process is not allowed. To restart, use: #{restart_hint}"
  when /\bclacky\s+server\b/
    raise SecurityError, "Managing the clacky server from within a session is not allowed. To restart, use: #{restart_hint}"
  when /^chmod\s+x/
    replace_chmod_command(command)
  when /^curl.*\|\s*(sh|bash)/
    replace_curl_pipe_command(command)
  when /^sudo\s+/
    block_sudo_command(command)
  when />\s*\/dev\/null\s*$/
    allow_dev_null_redirect(command)
  when /^(mv|cp|mkdir|touch|echo)\s+/
    validate_and_allow(command)
  else
    validate_general_command(@safe_check_command)
    command
  end
end