Skip to content

[fix] Block option injection in git worktree add + add subprocess deadline (#330)#345

Merged
lugassawan merged 6 commits into
mainfrom
bugfix/330-source-injection-subprocess-timeout
Jun 23, 2026
Merged

[fix] Block option injection in git worktree add + add subprocess deadline (#330)#345
lugassawan merged 6 commits into
mainfrom
bugfix/330-source-injection-subprocess-timeout

Conversation

@lugassawan

Copy link
Copy Markdown
Owner

Summary

  • Security (High): Add -- end-of-options separator to git worktree add in internal/git/worktree.go, preventing a user/config-supplied source value beginning with - from being parsed by git as a flag
  • Security (High): Introduce internal/gitref package with Validate() and ErrUnsafeRefName sentinel; validates default_source in config at load time and --source CLI flag at invocation time, giving a clear error before any subprocess runs
  • Reliability (Medium): Add ExecRunner.Timeout field in internal/git/git.go and mirror it in internal/gh/runner.go; a context.WithTimeout wraps every subprocess call, composing with the existing signal context via earliest-deadline-wins
  • Config: New command_timeout TOML field (e.g. "30s") with EffectiveCommandTimeout() accessor; defaults to 120 s; validated in Config.Validate(); threaded through all cmd/ call sites via newRunner(cmd.Context()) and newGHRunner(cmd.Context())
  • Refactor: gh.Default(timeout) now takes an explicit deadline; newRunner/newGHRunner in cmd/helpers.go source the timeout from config.FromContext(ctx) in one place

Test Plan

  • Unit tests pass (make test)
  • Linter passes (make lint)
  • E2E tests pass (make test-e2e)
  • Coverage ≥ 97% (go test ./... -coverprofile=... → 97.2%)
  • TestAddWorktreeInsertsDashDash — asserts -- appears before path/source in args
  • TestExecRunnerTimeoutExpires — nanosecond timeout causes context deadline exceeded
  • TestAddTaskRejectsUnsafeSource--source=-foo returns "leading dash" error
  • TestValidate (12 table cases) — all gitref allow/reject paths covered
  • TestEffectiveCommandTimeout, TestValidateCommandTimeout, TestMergeCommandTimeout

Issue

Closes #330

Shared stdlib-only validator used by both the security layer (A2)
and config validation (A4). Accepts the same character set as the
existing branchNameRe in gh/pr_meta.go.
A1: Add -- separator to git worktree add to prevent source/path being
    parsed as git options (mirrors AddWorktreeFromBranch and Checkout).
A3: Replace duplicated validateBranchName switch in gh/pr_meta.go with
    gitref.Validate, keeping PR-number and ghShapeHint decoration.
A4: Validate default_source in config.Validate() via gitref.Validate so
    a malicious .rimba/settings.toml is caught before any git call.

Closes part of #330.
…h timeout

B1: Config.CommandTimeout string field (toml command_timeout,omitempty),
    EffectiveCommandTimeout() with 120s default, Merge and Validate wired.
B2: ExecRunner.Timeout applies context.WithTimeout per RunInDir invocation;
    zero means no timeout (existing callers unaffected until B4 wires it).
B3: gh.Default(timeout) mirrors the same pattern for gh subprocess calls.

Composes with the existing signal context via earliest-deadline-wins.
Closes part of #330.
B4: newRunner(ctx) + newGHRunner(ctx) in helpers.go read timeout from
    config.FromContext(ctx); fall back to DefaultCommandTimeout (120s)
    when config is absent (bootstrap, completions, update_agent_hint).
B5: All ~30 cmd newRunner() → newRunner(cmd.Context()) call sites.
B6: gh construction sites use newGHRunner(cmd.Context()) uniformly;
    duplicate newGHRunner declaration removed from add.go.
B7: Test overrides updated to func(_ context.Context) git/gh.Runner.

MCP path (non-interactive) now gets the same 120s deadline as CLI.
- Add TestAddTaskRejectsUnsafeSource in cmd/add_test.go to cover the
  --source flag validation added in the previous commit
- Promote t.Errorf → t.Fatalf in TestExecRunnerTimeoutExpires so a
  failed string assertion stops the test immediately
- Clarify the gitref.go refNameRe comment to note it is a structural
  allowlist; git itself enforces trailing-dot / .lock / etc. rules
- Update CONTRIBUTING.md to reference the new newRunner(cmd.Context())
  and newGHRunner(cmd.Context()) signatures and gh.Default(timeout)
Comment thread cmd/add_test.go Outdated
Comment thread cmd/update_agent_hint.go Outdated
… comment

- Remove SetAnnotation(flagNoColor, BashCompOneRequiredFlag) from
  TestAddTaskRejectsUnsafeSource — it was misleading cargo-cult code
  that marked --no-color as required (harmless only because tests call
  RunE directly, bypassing Cobra flag validation)
- Add inline comment on newRunner(context.Background()) in
  resolvePostUpdateTipPaths to document the intentional fallback to
  DefaultCommandTimeout (no cobra cmd in scope at that call site)
@lugassawan lugassawan merged commit dcfd173 into main Jun 23, 2026
10 checks passed
@lugassawan lugassawan deleted the bugfix/330-source-injection-subprocess-timeout branch June 23, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[audit] git: --source option injection into git worktree add + missing subprocess deadline

1 participant