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
-
#git_result(out, err, status) ⇒ Object
Maps a finished git run onto the toolbox return contract.
-
#repo_relative(path) ⇒ Object
Resolve a path param to a repo-relative path, rejecting jail escapes.
- #run_git(*args, stdin: nil) ⇒ Object
- #valid_ref?(ref) ⇒ Boolean
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? = (err.empty? ? out : err).strip code = .match?(/not a git repository/i) ? :not_a_repo : :git_error error("git failed: #{}", 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
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 |