Module: RubyLLM::Toolbox::Tools::GitHelpers

Included in:
ApplyPatch, GitAdd, GitBlame, GitBranch, GitCheckout, GitCommit, GitDiff, GitGrep, GitLog, GitShow, GitStatus
Defined in:
lib/ruby_llm/toolbox/tools/git_helpers.rb

Overview

Shared behavior for the git tools. Mixed into each so they all run git the same hardened way.

Hardening matters here because git executes commands configured *by the repository* in several places, which turns “read-only” commands into RCE when operating on an untrusted checkout:

- core.fsmonitor runs a command during `git status`
- diff.external / textconv run commands during `git diff`

The read tools are on by default, so the runner neutralizes those (‘-c core.fsmonitor=`, and diff adds –no-ext-diff –no-textconv). The pager and credential prompts are also disabled so nothing can hang.

Constant Summary collapse

GIT_ENV =
{ "GIT_PAGER" => "cat", "GIT_TERMINAL_PROMPT" => "0" }.freeze
REF_RE =

A ref/branch the model supplies: no leading dash (option injection), and only the characters git refs legitimately use.

%r{\A[A-Za-z0-9][\w./\-]*\z}

Instance Method Summary collapse

Instance Method Details

#git_result(out, err, status) ⇒ Object

Maps a finished git run onto the toolbox return contract.



33
34
35
36
37
38
39
40
# File 'lib/ruby_llm/toolbox/tools/git_helpers.rb', line 33

def git_result(out, err, status)
  return error("git timed out after #{config.command_timeout}s", code: :timeout) if status == :timeout
  return out if status.exitstatus&.zero?

  message = (err.empty? ? out : err).strip
  code = message.match?(/not a git repository/i) ? :not_a_repo : :git_error
  error("git failed: #{message}", code: code)
end

#repo_relative(path) ⇒ Object

Resolve a path param to a repo-relative path, rejecting jail escapes. Returns nil for a blank path. Raises PathJail::Jailbreak on escape.



48
49
50
51
52
53
54
# File 'lib/ruby_llm/toolbox/tools/git_helpers.rb', line 48

def repo_relative(path)
  return nil if path.nil? || path.to_s.empty?

  jail = Safety::PathJail.new(config.fs_root)
  real = jail.resolve(path)
  Pathname.new(real).relative_path_from(Pathname.new(jail.root)).to_s
end

#run_git(*args, stdin: nil) ⇒ Object



27
28
29
30
# File 'lib/ruby_llm/toolbox/tools/git_helpers.rb', line 27

def run_git(*args, stdin: nil)
  argv = ["git", "-c", "core.fsmonitor=", "-C", config.fs_root, "--no-pager", *args]
  ProcessRunner.capture(argv, env: git_env, stdin: stdin, timeout: config.command_timeout, unsetenv_others: true)
end

#valid_ref?(ref) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/ruby_llm/toolbox/tools/git_helpers.rb', line 42

def valid_ref?(ref)
  ref.to_s.match?(REF_RE)
end