Skip to content

feat(bash): read the commands that a bash script runs - #31

Merged
Bakhomious merged 1 commit into
mainfrom
step/bash
Nov 3, 2026
Merged

Bakhomious merged 1 commit into
mainfrom
step/bash

Conversation

@Bakhomious

Copy link
Copy Markdown
Contributor

Brief

First part of the guard (split into bash, guard, guard-orchestrator and belt, decision 50). The guard has to know which commands a bash tool call runs; a text match misses git -C x push, nohup git push or $(git push) and catches echo 'git push'. The standard library has no bash parser, so internal/bash uses mvdan.cc/sh/v3 (the user's OK via nudge, pinned exactly at v3.14.1, BSD-3).

What changes

  • bash.Calls(script) gives each command that the script runs, as the words that bash passes it:
    • quotes and backslashes taken off; each part that only the run decides ($X, $(…), $'…') becomes the marker bash.Dynamic;
    • commands in substitutions, here-documents, functions and conditions too;
    • past wrappers (builtin caffeinate command env exec nice nohup stdbuf sudo time timeout xargs, and Homebrew's g names): their options, NAME=value words and timeout's duration;
    • the scripts of eval, and of bash dash ksh sh zsh with -c;
    • a script that doesn't parse, or a nested one, is an error.
  • bash.Name(word): a program's name, lower-cased (a Mac's file system ignores case).
  • Only internal/bash imports the module.
  • Docs: rows for the split, decision 50, fact 33, risk 2, the Safety paragraph, and the homebrew row (the release carries mvdan.cc/sh's BSD-3 notice).

Acceptance

  • Table tests for each rule; 100% coverage of the package.
  • QA probes: 72 mutants, all caught (two survivors fixed with new rows).
  • Smoke: 32,473 distinct bash commands from real Copilot CLI sessions: one parse error (bash's old $[…] arithmetic, on which bash fails too), 0.2 s for all.
  • mise run check green.

Notes (non-blocking)

  • Known misses, listed in decision 50: aliases, script files, a variable as the program, scripts on stdin, other languages, find -exec, git aliases, brace expansion, env -S, long options with a separate value. The guard is a seatbelt (risk 2); belt checks a second one.
  • Nothing calls the package yet; guard does.

internal/bash reads a script with mvdan.cc/sh's bash parser (the user's
OK, pinned at v3.14.1) and returns each command that it runs, as the
words that bash passes it: in substitutions, here-documents and
functions too, past wrappers such as env, nohup and xargs, and inside
the scripts of sh -c and eval. The guard builds on it.

Docs: the guard split into bash, guard, guard-orchestrator and belt;
decision 50, fact 33, risk 2, and the homebrew row's license notice.
@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

@Bakhomious
Bakhomious merged commit 22b1893 into main Nov 3, 2026
3 checks passed
@Bakhomious
Bakhomious deleted the step/bash branch November 3, 2026 19:37
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