Repository navigation
fix: refuse an rm whose path starts with a variable that can be empty - #889
Merged
Merged
Conversation
…h through
An agent's `rm -f "$VAR"/*` raises the host's own permission prompt,
which the person cannot judge. The product thinker chose a guard entry
that blocks the shape with the `${VAR:?}` rewrite as its successor, plus
the same rule taught to agents, built after the v0.13.4 cut.
Refs: iss-2610091942156774
Assisted-by: Claude:claude-opus-5-5
An agent's rm -f "$VAR"/* raised the host's own dangerous-rm prompt, which
the person had to approve without knowing whether VAR was empty, while the
guard let the shape through. The bundled registry now carries the blocker
rm-unguarded-variable-path. It refuses an rm whose operand starts with a
variable that can be empty directly followed by a slash ("$VAR"/x, $VAR/x,
"${VAR}"/x, ${VAR}/x and "$VAR/x"), inside a string a shell runs as on the
line, and its successor is the rewrite "${VAR:?}"/... or a literal path.
The guard never rewrites the command itself.
The pattern language gains one operand predicate, arg_shapes, whose only
shape is unguarded-variable-path. The tokenizer decides it per word from
the expansions it recorded, because the written spelling cannot tell
${VAR:?} from ${VAR}. A default that cannot be empty, a variable later in
the path, a bare "$VAR", a trim or other transform, and $HOME and $PWD are
not refused.
The SHELL domain teaches the entry from the registry. Every agent
definition that can run a shell, except the scribe, whose definition is
held to ledger content, carries a block generated from the same entry and
kept in step by a test, with its prompt_version bumped and a changelog
entry. Two repository scripts (hooks/bootstrap.sh, scripts/preflight-receipt.sh)
that removed under a variable that is never empty now spell it with :?, so
running them is not refused.
Resolves: iss-2610091942156774
Assisted-by: Claude:claude-opus-5-5
The review of the new entry found the successor's own rewrite refused inside a double-quoted shell string, a nested default guard not read, and a variable followed by a substitution not followed. All three are minor and recorded for a follow-up rather than widening this change. Refs: iss-2610100938485695 Assisted-by: Claude:claude-opus-5-5
Main took question-drafter to 0.1.1 for the question-chip change while this branch took it to 0.1.1 for the guard-lessons block, so the merged definition changed without a bump. This branch's entry becomes 0.1.2. Assisted-by: Claude:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An agent's
rm -f "$VAR"/*makes the host raise its own dangerous-rm prompt: if$VARis empty, the path becomes/*. The person cannot tell whether the variable is set, so they approve blind. abcd's guard let the shape through, and sub-agents never receive the injected SHELL rules, so nothing steered an agent to the safe form first.What changes
rm-unguarded-variable-path. It refuses anrmwhose operand starts with a variable that can be empty followed by/:"$VAR"/…,$VAR/…,"${VAR}"/…,"$VAR/…",${VAR:-}/…,"$1"/…. Its successor is"${VAR:?}"/…or a literal path. The guard refuses and never rewrites the command."${VAR:?}"/…, non-empty defaults ("${TMPDIR:-/tmp}"/…), trims, a bare"$VAR"operand, a variable later in the path (./build/$name), and$HOME/$PWD(covered by the existing rm entries) are allowed.arg_shapes, in the registry's pattern language. The tokenizer now records per expansion whether its operator guarantees a non-empty value (:?,:-,:=) or transforms it.sh -cstrings are read the same way.guard-lessonsblock holding the same lesson text, andagentlessons_test.gorefuses a missing or stale block (-updateregenerates it). Those agents get a PATCHprompt_versionbump with an agents CHANGELOG entry, as the agent-contract lint requires.commands/guard.mddescribes the entry."$BUILD_DIR"/*-style shapes as allowed now expect the block.hooks/bootstrap.shandscripts/preflight-receipt.shuse${VAR:?}on variables that are provably never empty, so their behaviour is unchanged.Review
An independent review read the diff and probed about 80 inputs, then returned SHIP. It raised three minor notes, captured for a follow-up: the successor's own form is refused inside a double-quoted
sh -cstring, a nested default guard is not read, and a variable followed by a substitution is not followed.Still to check by hand: in a live host session, the guard's refusal should land before the host's own prompt.
make preflightis clean on8458cdd52.Resolves: iss-2610091942156774
Refs: iss-2610100938485695
Assisted-by: Claude:claude-opus-5-5