Development record
guard CHANGELOG code comment and test claim python -c and perl -e already get a loud warn but the shipped behaviour is a silent allow and the pinning test is vacuous
guard CHANGELOG code comment and test claim python -c and perl -e already get a loud warn but the shipped behaviour is a silent allow and the pinning test is vacuous
Evidence
CHANGELOG.md:126 (released 0.6.0, present tense): "python -c and perl -e carry source this tokenizer cannot read, and guessing a verdict on them would be worse than the loud warn they already get." False: abcd guard check --command 'python3 -c "...os.system(git push --force)..."' → {"verdict":"allow"}, no warn. Same false claim at payload.go:186-187 and interpreters_test.go:105-106. TestNonShellInterpretersStayOutOfTheFamily asserts only Verdict != Block — mutation-proven vacuous (passes with python/perl added to isShellFamily).
Adversarial verdict: CONFIRMED as a doc+test defect (minor)
Two independent refuters confirmed. The silent-allow BEHAVIOUR is defensible per adr-42 (a non-shell interpreter payload is one opaque token; warning on every python -c is the warn-storm adr-42 avoids, and the storm is unmeasurable in this Go repo). What is wrong is (a) a released CHANGELOG stating a false posture, (b) the same falsehood at the two sites a maintainer consults, (c) a test that guards nothing. Fix (autonomous, honesty only): reword CHANGELOG:126 + payload.go + interpreters_test.go comments to "recorded but not yet implemented", add the case to the "what an allow does not see" lists (17-guard.md / commands/guard.md / docs/reference/cli/commands.md), and de-vacuum the test with a discriminating fixture asserting VerdictAllow. Implementing an actual warn posture is OUT of scope (needs re-labelling adr-42's discharged corpus evidence). Not prior art: no ledger issue names python/perl.
Frontmatter
| id | iss-315 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-315-guard-changelog-code-comment-and-test.md
open on GitHub ↗ · commit history ↗
mentions5
- adr-42The guard's parse layer is a mistake filter, not a security boundary — matching is two-tier, and completeness is abandoned as a goal
- dcn-26082000000000032026-08-20 — Bug-hunt round 1 (hunt A), landed late: the round ran 2026-08-19 on a branch that
- dcn-26082200000000092026-08-22 — All-dimensions bug-hunt round 7 (branch `bughunt-b/round-7`).
- iss-2609011424113262The brief's surface prose has drifted from the shipped binary in twenty-one
- iss-2609012040008545GHSA-3w99-pgv4-8g55 (draft advisory, severity low; CWE-184, Incomplete List of Disallowed Inputs): a hazard whose argv bash rewrites by glob expansion is an allow. `tokenize` (internal/core/guard/tokenize.go) has no glob branch — `*`, `?` and `[...]` fall to the default word branch and quotes are stripped as they are read, so nothing records that a metacharacter was unquoted — and `matchSegment` (internal/core/guard/match.go) compares the command name, operands 0/1 and every flag alternative literally. So `git pus? --force origin main`, `git push --forc? origin main`, `g?t push --force origin main`, `git push --for[c]e origin main`, `git push --force* origin main` (which expands to --force-with-lease, a member of the entry's own flag group) and the same spellings inside `sh -c` / `eval` payloads all allow, while a file named push, --force or git in the working directory makes bash run the hazard. Tier 2 cannot reach either shape: a flag is never a speculative start and a globbed command name resolves to a name no entry knows. The fix must record per token that it carried an unquoted, unescaped glob metacharacter, and at the positions an entry constrains — command name, subcommand/subcommand2, flag and flag-value alternatives — treat a literal the glob CAN produce (path.Match, decidable from the string with no filesystem read and no cwd) as produced, the fail-closed reading: the guard cannot see the post-cd directory and a file named for the hazard is the cheapest condition. Unconstrained positions are untouched, so `ls *`, `git add *.md`, the quoted and backslash-escaped spellings and the repo corpus stay allow; a segment behind the zsh `noglob` wrapper skips the compare. Siblings named as out of scope: the `wrappers` lookup in commandOf (`sud? rm -rf *` reaches Tier 2's warn, acceptable); the `isShellFamily`, execStringVerbs and singleStringLaunchers lookups (a globbed interpreter name keeps its payload one opaque token, a silent allow of iss-315's shape); `pathArgMatches` (a path glob needs a directory tree named for it in cwd); the `--flag=value` and short-cluster forms of a globbed flag and extended globs (extglob, `**`), which path.Match does not model — the simple compare is a floor; `set -f` as a separate segment (over-block direction, rare); tilde, $VAR and $(…), which need state outside the string and are adr-42's documented residual.