Skip to content

Commit 01d23d3

Browse files
committed
refactor: clean adapter parsing and init trust helpers
1 parent 7b90ec6 commit 01d23d3

File tree

8 files changed

+318
-184
lines changed

8 files changed

+318
-184
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ Review and approve hook commands defined in the repository's `.gtrconfig` file.
348348
git gtr trust # Review and approve .gtrconfig hooks
349349
```
350350

351-
Trust is stored per content hash and must be re-approved if hooks change. Hooks from your local git config (`.git/config`, `~/.gitconfig`) are always trusted.
351+
Trust is stored per repository path plus hook definitions and must be re-approved if hooks change. Hooks from your local git config (`.git/config`, `~/.gitconfig`) are always trusted.
352352

353353
### Other Commands
354354

@@ -369,6 +369,14 @@ git gtr config set gtr.editor.default cursor
369369
# Set your AI tool (aider, auggie, claude, codex, continue, copilot, cursor, gemini, opencode)
370370
git gtr config set gtr.ai.default claude
371371

372+
# Override-backed adapters may include flags
373+
git gtr config set gtr.editor.default "nano -w"
374+
git gtr config set gtr.ai.default "claude --continue"
375+
376+
# Generic fallbacks may use other safe PATH commands
377+
git gtr config set gtr.editor.default "code --wait"
378+
git gtr config set gtr.ai.default "bunx @github/copilot@latest"
379+
372380
# Copy env files to new worktrees
373381
git gtr config add gtr.copy.include "**/.env.example"
374382

@@ -402,6 +410,8 @@ git gtr config set gtr.ui.color never
402410

403411
**Hook trust:** Hooks defined in `.gtrconfig` require explicit approval before they execute. Run `git gtr trust` after cloning a repository or when `.gtrconfig` hooks change. This protects against malicious hook injection in shared repositories.
404412

413+
**Adapter safety:** Generic `gtr.editor.default` and `gtr.ai.default` values must resolve to safe PATH commands. Filesystem paths such as `./tool` and shell wrapper forms such as `sh -c ...` are rejected. Override-backed adapters like `claude`, `cursor`, and `nano` may include additional flags, for example `claude --continue` or `nano -w`.
414+
405415
**Configuration precedence** (highest to lowest):
406416

407417
1. `git config --local` (`.git/config`) - personal overrides

lib/adapters.sh

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ EOF
150150

151151
# Generic adapter functions (used when no explicit adapter file exists)
152152
# These will be overridden if an adapter file is sourced
153-
# Globals set by load_editor_adapter: GTR_EDITOR_CMD, GTR_EDITOR_CMD_NAME
153+
# Globals set by load_editor_adapter:
154+
# GTR_EDITOR_CMD, GTR_EDITOR_CMD_NAME, GTR_EDITOR_CMD_ARGS
154155
editor_can_open() {
155156
command -v "$GTR_EDITOR_CMD_NAME" >/dev/null 2>&1
156157
}
@@ -166,28 +167,50 @@ editor_open() {
166167
target="$workspace"
167168
fi
168169

169-
_run_configured_command "$GTR_EDITOR_CMD" "$target"
170+
_run_configured_command "$GTR_EDITOR_CMD_NAME" "${GTR_EDITOR_CMD_ARGS[@]}" "$target"
170171
}
171172

172-
# Globals set by load_ai_adapter: GTR_AI_CMD, GTR_AI_CMD_NAME
173+
# Globals set by load_ai_adapter:
174+
# GTR_AI_CMD, GTR_AI_CMD_NAME, GTR_AI_CMD_ARGS
173175
ai_can_start() {
174176
command -v "$GTR_AI_CMD_NAME" >/dev/null 2>&1
175177
}
176178

177179
ai_start() {
178180
local path="$1"
179181
shift
180-
(cd "$path" && _run_configured_command "$GTR_AI_CMD" "$@")
182+
(cd "$path" && _run_configured_command "$GTR_AI_CMD_NAME" "${GTR_AI_CMD_ARGS[@]}" "$@")
183+
}
184+
185+
# Assign an array to a caller-provided variable name.
186+
# Bash 3.2 has no namerefs, so this uses a safely quoted eval assignment.
187+
_set_array_var() {
188+
local var_name="$1"
189+
shift
190+
191+
case "$var_name" in
192+
[a-zA-Z_][a-zA-Z0-9_]*) ;;
193+
*) return 1 ;;
194+
esac
195+
196+
local assignment="${var_name}=("
197+
local item
198+
for item in "$@"; do
199+
assignment="${assignment}$(printf '%q ' "$item")"
200+
done
201+
assignment="${assignment})"
202+
203+
eval "$assignment"
181204
}
182205

183206
# Split a config-supplied command string without shell evaluation.
184-
# Populates the global _GTR_PARSED_CMD_ARGS array.
207+
# Usage: _parse_configured_command <out_array_name> <command_string>
185208
_parse_configured_command() {
186-
local command_string="$1"
209+
local out_var="$1"
210+
local command_string="$2"
187211
local length="${#command_string}"
188212
local i=0 char="" token="" state="normal" escaped=0 token_started=0
189-
190-
_GTR_PARSED_CMD_ARGS=()
213+
local parsed_tokens=()
191214

192215
while [ "$i" -lt "$length" ]; do
193216
char="${command_string:$i:1}"
@@ -206,7 +229,7 @@ _parse_configured_command() {
206229
;;
207230
" " | $'\t' | $'\n')
208231
if [ "$token_started" -eq 1 ]; then
209-
_GTR_PARSED_CMD_ARGS+=("$token")
232+
parsed_tokens+=("$token")
210233
token=""
211234
token_started=0
212235
fi
@@ -263,10 +286,11 @@ _parse_configured_command() {
263286
[ "$state" = "normal" ] || return 1
264287

265288
if [ "$token_started" -eq 1 ]; then
266-
_GTR_PARSED_CMD_ARGS+=("$token")
289+
parsed_tokens+=("$token")
267290
fi
268291

269-
[ "${#_GTR_PARSED_CMD_ARGS[@]}" -gt 0 ]
292+
[ "${#parsed_tokens[@]}" -gt 0 ] || return 1
293+
_set_array_var "$out_var" "${parsed_tokens[@]}"
270294
}
271295

272296
_configured_command_uses_path_arg() {
@@ -289,7 +313,8 @@ _configured_command_is_wrapper() {
289313
return 1
290314
}
291315

292-
_validate_configured_command() {
316+
# Reject raw shell syntax before argv parsing.
317+
_configured_command_has_safe_syntax() {
293318
local command_string="$1"
294319

295320
# Reject shell metacharacters in config-supplied command names to prevent injection.
@@ -299,40 +324,37 @@ _validate_configured_command() {
299324
return 1
300325
;;
301326
esac
327+
}
302328

303-
_parse_configured_command "$command_string" || return 1
304-
[ "${#_GTR_PARSED_CMD_ARGS[@]}" -gt 0 ] || return 1
329+
_configured_command_is_safe() {
330+
[ "$#" -gt 0 ] || return 1
305331

306-
local cmd_name="${_GTR_PARSED_CMD_ARGS[0]}"
332+
local cmd_name="$1"
333+
shift
307334

308335
case "$cmd_name" in
309336
*/* | *\\*)
310337
return 1
311338
;;
312339
esac
313340

314-
if _configured_command_is_wrapper "$cmd_name" && [ "${#_GTR_PARSED_CMD_ARGS[@]}" -gt 1 ]; then
341+
if _configured_command_is_wrapper "$cmd_name" && [ "$#" -gt 0 ]; then
315342
return 1
316343
fi
317344

318345
local arg
319-
for arg in "${_GTR_PARSED_CMD_ARGS[@]:1}"; do
346+
for arg in "$@"; do
320347
if _configured_command_uses_path_arg "$arg"; then
321348
return 1
322349
fi
323350
done
324351
}
325352

326-
# Parse and run a config-supplied command string while preserving quoted args.
353+
# Run a config-supplied command argv that has already been parsed and validated.
354+
# Usage: _run_configured_command <argv...>
327355
_run_configured_command() {
328-
local command_string="$1"
329-
shift
330-
local extra_args=("$@")
331-
332-
_parse_configured_command "$command_string" || return 1
333-
[ "${#_GTR_PARSED_CMD_ARGS[@]}" -gt 0 ] || return 1
334-
335-
"${_GTR_PARSED_CMD_ARGS[@]}" "${extra_args[@]}"
356+
[ "$#" -gt 0 ] || return 1
357+
"$@"
336358
}
337359

338360
# Standard AI adapter builder — used by adapter files that follow the common pattern
@@ -446,14 +468,17 @@ resolve_workspace_file() {
446468
# Usage: _load_adapter <type> <name> <label> <builtin_list> <path_hint>
447469
_load_adapter() {
448470
local type="$1" name="$2" label="$3" builtin_list="$4" path_hint="$5"
449-
if ! _validate_configured_command "$name"; then
471+
local parsed_args=()
472+
if ! _configured_command_has_safe_syntax "$name" \
473+
|| ! _parse_configured_command parsed_args "$name" \
474+
|| ! _configured_command_is_safe "${parsed_args[@]}"; then
450475
log_error "$label '$name' is not a safe executable command"
451476
log_info "Use a PATH command name, optionally with flags (e.g., 'code --wait')"
452477
return 1
453478
fi
454479

455-
local adapter_selector="${_GTR_PARSED_CMD_ARGS[0]}"
456-
local cmd_args=("${_GTR_PARSED_CMD_ARGS[@]:1}")
480+
local adapter_selector="${parsed_args[0]}"
481+
local cmd_args=("${parsed_args[@]:1}")
457482

458483
local adapter_file="$GTR_DIR/adapters/${type}/${adapter_selector}.sh"
459484

@@ -509,11 +534,13 @@ _load_adapter() {
509534
# Set globals for generic adapter functions
510535
# Note: $name may contain arguments (e.g., "code --wait", "bunx @github/copilot@latest")
511536
if [ "$type" = "editor" ]; then
537+
# shellcheck disable=SC2034 # Exposed for adapter state/introspection.
512538
GTR_EDITOR_CMD="$name"
513539
GTR_EDITOR_CMD_NAME="$cmd_name"
514540
# shellcheck disable=SC2034 # Used by sourced override adapters.
515541
GTR_EDITOR_CMD_ARGS=("${cmd_args[@]}")
516542
else
543+
# shellcheck disable=SC2034 # Exposed for adapter state/introspection.
517544
GTR_AI_CMD="$name"
518545
GTR_AI_CMD_NAME="$cmd_name"
519546
# shellcheck disable=SC2034 # Used by sourced override adapters.

lib/commands/help.sh

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ Special:
5757
5858
Available editors:
5959
antigravity, atom, cursor, emacs, idea, nano, nvim, pycharm, sublime, vim,
60-
vscode, webstorm, zed, none (or any command in your PATH)
60+
vscode, webstorm, zed, none (or any safe command in your PATH)
6161
6262
Examples:
6363
git gtr editor my-feature # Uses default editor
6464
git gtr editor my-feature --editor vscode # Override with vscode
65+
git gtr editor my-feature --editor "nano -w" # Override-backed adapter with flags
6566
git gtr editor 1 # Open main repo
6667
EOF
6768
}
@@ -84,11 +85,12 @@ Special:
8485
8586
Available AI tools:
8687
aider, auggie, claude, codex, continue, copilot, cursor, gemini,
87-
opencode, none (or any command in your PATH)
88+
opencode, none (or any safe command in your PATH)
8889
8990
Examples:
9091
git gtr ai my-feature # Uses default AI tool
9192
git gtr ai my-feature --ai aider # Override with aider
93+
git gtr ai my-feature --ai "claude --continue"
9294
git gtr ai my-feature -- --verbose # Pass args to AI tool
9395
git gtr ai 1 # AI in main repo
9496
EOF
@@ -255,9 +257,14 @@ Examples:
255257
git gtr config # List all config
256258
git gtr config list --local # List local config only
257259
git gtr config set gtr.editor.default cursor # Set default editor
260+
git gtr config set gtr.editor.default "code --wait"
258261
git gtr config add gtr.copy.include ".env*" # Add copy pattern
259262
git gtr config get gtr.ai.default # Get AI tool setting
260263
git gtr config unset gtr.worktrees.prefix # Remove prefix setting
264+
265+
Generic editor/AI defaults must be safe PATH commands. Filesystem paths
266+
such as ./tool and shell-wrapper forms such as sh -c ... are rejected.
267+
Override-backed adapters like claude, cursor, and nano may include flags.
261268
EOF
262269
}
263270

@@ -283,8 +290,10 @@ git gtr adapter - List available adapters
283290
Usage: git gtr adapter
284291
285292
Lists all built-in editor and AI tool adapters, along with their availability
286-
on the current system. Any command in your PATH can also be used as an
287-
editor or AI tool without a built-in adapter.
293+
on the current system. Safe PATH commands can also be used without a built-in
294+
adapter. Path-based commands like ./tool and shell wrappers like sh -c are
295+
rejected, while override-backed adapters such as claude, cursor, and nano may
296+
include flags.
288297
289298
Examples:
290299
git gtr adapter # Show all adapters
@@ -411,8 +420,9 @@ Reviews and approves hook commands defined in the repository's .gtrconfig
411420
file. Hooks from .gtrconfig are not executed until explicitly trusted.
412421
413422
This prevents malicious contributors from injecting arbitrary shell
414-
commands via shared .gtrconfig files. Trust is stored per content hash
415-
in ~/.config/gtr/trusted/ and must be re-approved if hooks change.
423+
commands via shared .gtrconfig files. Trust is stored per repository
424+
path plus hook definitions in ~/.config/gtr/trusted/ and must be
425+
re-approved if hooks change.
416426
417427
Hooks from your local git config (.git/config, ~/.gitconfig) are always
418428
trusted since you control those files directly.
@@ -582,7 +592,7 @@ SETUP & MAINTENANCE:
582592
583593
adapter
584594
List available editor & AI tool adapters
585-
Note: Any command in your PATH can be used (e.g., code-insiders, bunx)
595+
Note: Safe PATH commands can be used (e.g., code --wait, bunx @github/copilot@latest)
586596
587597
clean [options]
588598
Remove stale/prunable worktrees and empty directories

0 commit comments

Comments
 (0)