Skip to content

fix: read the two shapes a shell string hid from the rm guard entry - #927

Merged
REPPL merged 1 commit into
mainfrom
fix/guard-string-reread-gaps
Oct 11, 2026
Merged

REPPL merged 1 commit into
mainfrom
fix/guard-string-reread-gaps

Conversation

@REPPL

@REPPL REPPL commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

What

This closes the two gaps #909 left in the rm-unguarded-variable-path guard entry. In both, a double-quoted shell string hid the shape from the entry.

  • An alternative that expands to nothing. With X unset, the enclosing shell expands ${X:+x} to nothing, so sh -c "rm -rf ${X:+x}/y" hands the string rm -rf /y. The string's re-read wrote that spot as nothing, so no reading opened with a variable. The lead reading now writes it as ${_}, a reference that can be empty, so the command refuses. That reading only decides whether a word opens with a variable; the value compare never sees it.
  • A default written as an escaped reference. sh -c "rm -rf ${X:-\${Y}}/y" hands the string rm -rf ${Y}/y, because the default ends at its first }. The re-read's word placed that brace inside the variable, so its shape did not fit the string's word and was never paired. A word now also pairs when its written text fits.

Cost

The lead reading is written out a second time only for a string where a site that can print nothing has a / after it. Dropping that gate put the guard at 29 units of work per byte against a bar of 24. A new shape in TestSpellingSetsStayLinear pins the gated cost.

Review

A Fable review returned SHIP. It ran every verdict case in the guard's tests (1,030 commands) plus 88 crafted shapes against both main and this branch. 36 verdicts changed, all from allow or warn to block, and none the other way. Among them are correct new home-directory refusals: sh -c "rm -rf ${X:-\${HOME}}/" now refuses as rm-rf-root-or-home.

Its two test notes and one older gap it found in kill-by-search follow in a separate change.

Resolves: iss-2610110025455728
Refs: iss-2610091942156774

Assisted-by: Claude:claude-opus-5-5

The enclosing shell expands an alternative to nothing when its variable is
unset, so `sh -c "rm -rf ${X:+x}/y"` hands the string `rm -rf /y`. The
string was written out with nothing there, so no reading led with a
variable. The lead reading now writes an empty text as `${_}`, a reference
that can be empty, and is asked even when no named reading leads, but only
for a string where such a site has a `/` after it, so the cost bar holds; a
new shape in TestSpellingSetsStayLinear pins that.

A default written as an escaped reference ends at its first `}`:
`sh -c "rm -rf ${X:-\${Y}}/y"` hands the string `rm -rf ${Y}/y`. The
reading's word marks the brace as the variable's, so its marked shape did
not fit the string's word and it was never paired. A word now pairs when
its written text fits, too.

Resolves: iss-2610110025455728

Assisted-by: Claude:claude-opus-5-5
@REPPL
REPPL added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 731f887 Oct 11, 2026
13 checks passed
@REPPL
REPPL deleted the fix/guard-string-reread-gaps branch October 11, 2026 14:32
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