Skip to content

fix: prevent shell injection and broken commands in run() callers#289

Open
TerminalGravity wants to merge 5 commits intomainfrom
fix/shell-exec-bugs
Open

fix: prevent shell injection and broken commands in run() callers#289
TerminalGravity wants to merge 5 commits intomainfrom
fix/shell-exec-bugs

Conversation

@TerminalGravity
Copy link
Collaborator

Problem

Several tools were passing shell syntax to run(), which uses execFileSync (no shell). This caused silent failures in production:

  • Shell redirections (2>/dev/null) passed as literal git args → git errors
  • Pipe chains (| grep, | tail) passed as literal git args → wrong output
  • git prefix doubled (run("git diff ...")execFileSync("git", ["git", "diff", ...]))
  • Non-git commands (find, gh, pnpm) routed through execFileSync("git", ...)

Fix

  • shell() helper — new export from git.ts using execSync for commands that need shell features (pipes, redirects, non-git commands)
  • run() hardened — string parsing now strips leading git, removes 2>/dev/null, strips pipe/fallback chains
  • 10 call sites migrated across 8 tools to use shell() or proper array syntax
  • 13 new tests for run() cleanup logic and shell() behavior

Affected tools

audit-workspace, clarify-intent, enrich-agent-task, sequence-tasks, session-handoff, session-health, verify-completion

Tests

68 passing (was 55) — all green.

Adds a ready-to-use CLAUDE.md template that makes Claude Code
automatically run preflight_check on prompts. Users can copy it
into their project to get preflight working without manual tool calls.

Referenced from Quick Start in README and examples/README.
- CLI now responds to --help/-h with usage info, profiles, and links
- CLI now responds to --version/-v with package version
- Previously, any flag just launched the interactive wizard
- Fixed README badge from Node 18+ to Node 20+ (matches engines field)
Adds a new 'export_timeline' MCP tool that generates markdown reports
from timeline data with:
- Summary stats table (events by type, correction rate, commits/prompt)
- ASCII activity chart grouped by day
- Recent commits log
- Correction insights
- Error summary
- Configurable period (day/week/month) with offset
- Optional save to ~/.preflight/reports/

Includes tests (2 passing).

Closes #5
Adds 10 tests covering:
- Trivial prompt pass-through
- force_level=skip bypass
- Ambiguous prompt detection (vague pronouns, short prompts, vague verbs)
- Multi-step execution plan generation
- Git state inclusion in clarification
- Triage confidence/reasons display
- Pattern match triage boosting
- Risk level assignment in execution plans

Brings test count from 45 to 55.
Several tools were passing shell syntax (pipes, redirects, || chains) and
non-git commands (cat, find, pnpm, gh) to run(), which uses execFileSync
without a shell. This caused silent failures:

- Shell redirections (2>/dev/null) passed as literal git args
- Pipe chains (| grep, | tail) passed as literal git args
- 'git' prefix doubled (run already prepends 'git')
- Non-git commands (find, gh, pnpm) routed through git execFileSync

Fix:
- Add shell() helper using execSync for commands needing shell features
- Harden run() string parsing to strip leading 'git' and shell syntax
- Migrate 10 call sites across 8 tools to use shell() or proper arrays
- Add 13 tests covering run() cleanup and shell() behavior

Affected tools: audit-workspace, clarify-intent, enrich-agent-task,
sequence-tasks, session-handoff, session-health, verify-completion
Copy link
Collaborator Author

@TerminalGravity TerminalGravity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid fix. The shell() function with execSync is the right escape hatch for callers that genuinely need pipes/redirects, while run() stays injection-safe with execFileSync. Tests cover the important edge cases. One thought: might want to add a comment on shell() noting it should only be used for trusted command strings (never user input).

Copy link
Collaborator Author

@TerminalGravity TerminalGravity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid fix. The shell() utility is the right call — execFileSync cannot handle pipes or redirects, and callers were passing shell syntax that would break silently. The regex stripping in run() is good backward-compat. LGTM, ready to merge.

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.

1 participant