Files
stack/packages
Hermes Agent df83a9eec2
ci/woodpecker/pr/ci Pipeline was successful
wrapper-guard: recognize program names after quote removal, in one place
Round-four remediation of both blockers gate-ultron-01 raised on d99ff57e.
Measured against that head before anything was touched; all seven returned
rc=0, and each executes the program the check exists to recognize:

    "/usr/bin/curl" --config /tmp/w.cfg          -> allowed
    './curl' --config /tmp/w.cfg                 -> allowed
    $(which curl) --config /tmp/w.cfg            -> allowed
    `which curl` --config /tmp/w.cfg             -> allowed
    /usr/bin/gh api -X POST repos/a/b/issues …   -> allowed
    ./gh api -X POST repos/a/b/issues …          -> allowed
    /usr/local/bin/tea api -X POST repos/a/b/… … -> allowed

This is the third appearance of one defect, and the shape is worth stating
plainly because the first two repairs each fixed an INSTANCE and left the
class: the check matched the bare word, then it matched the unquoted
basename. Both were models of one TEXTUAL PRESENTATION of a shell word
rather than of the word, so the first repair was defeated by an absolute
path and the second by two quote characters. Recognizing a name is either
done after quote removal or it is caller-name parsing wearing a longer
regex.

The second blocker is the same defect sitting untouched in the API SCOPE
gate the whole time, while the curl arm was repaired twice beside it. That
one is worse than it looks: the scope gate decides whether write detection
runs AT ALL, so failing to admit `/usr/bin/gh api -X POST` is not a missed
match, it is an allow. No URL marker rescued those commands either —
provider CLI endpoints are spelled `repos/…` with no leading slash, so
`/repos/` never matched them.

Fix, and the reason it is one fix rather than two:

  - $CMD_NAMES — a second reading of the same command with quote and
    substitution punctuation turned into whitespace. Names are read from it.
  - $NAME_PREFIX — the one place the shape of a program name is written
    down. Both callers use it, so the next fix to this class lands in a
    single location instead of whichever arm review happened to probe. That
    is the actual lesson of finding this defect twice in one file.

The prefix still must end at a slash. `mycurl` and `curl-wrapper` are
different programs and blocking them is the over-block that gets a guard
routed around instead of repaired; both remain negative fixtures, and
`mygh` and an absolute-path READ join them.

The cost is the one this file already chose and documented for the payload
check: quoting an example does not exempt it, so writing one of these
commands inside quotes on a Bash line is refused too. Applying that rule to
the name arms makes the file coherent — the alternative is a guard where
the payload arm treats quotes as text and the name arms treat them as
armour.

Still open, stated rather than left to be found: a name absent from the
text — assembled from variables, or reached through a wrapper script that
execs the program — is invisible here. That is a limit of inspecting a
command string, not something a pattern closes.

Controls: the 7 positive fixtures FAIL at d99ff57e and pass here; the 4
negative fixtures pass at BOTH heads, so they measure over-blocking rather
than decorate the diff. Suite 157/157.
2026-08-13 03:12:39 -05:00
..