Skip to content

fix: read the edge shapes of the unguarded variable path guard entry - #909

Merged
REPPL merged 7 commits into
mainfrom
fix/guard-rm-varpath-edges
Oct 11, 2026
Merged

REPPL merged 7 commits into
mainfrom
fix/guard-rm-varpath-edges

Conversation

@REPPL

@REPPL REPPL commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

What

The rm-unguarded-variable-path guard entry from #889 now reads the three edge shapes its review found, plus two more found in this branch's review.

  • The successor inside a double-quoted shell string. sh -c "rm -rf ${X:?}/y" was refused, because the string's re-read spelled ${X:?} as ${X}. It is now allowed, and so is a nested guard (${VAR:-${OTHER:?}}).
  • A variable before a command substitution. rm -rf $VAR$(true)/x is now refused.
  • A default that splits away. With X unset, bash, sh and dash turn rm -rf ${X:- }/y into rm -rf /y, and ${X:-a }/y into a and /y. Both are now refused. This was allowed since fix: refuse an rm whose path starts with a variable that can be empty #889. A quoted, escaped or $'\x20' blank stays text and stays allowed.
  • A default the string's shell empties. In sh -c "rm -rf ${X:-\"\"}/y", ${X:- } and ${X:-\$(true)}, the inner shell strips the quotes, splits the blank or runs the substitution. The site is written as its guard only when nothing it prints can be emptied there; otherwise it keeps ${X} and refuses, as main does.

Not in this change

  • sh -c "rm -rf ${X:+x}/y" and sh -c "rm -rf ${X:-\${Y}}/y" are still allowed, as they are on main. Both come from how the string's re-read pairs a word it writes out, which is a separate mechanism, captured in the record below.
  • sh -c "rm -rf ${X:-\\ }/y" and sh -c "rm -rf \"${X:-my dir}\"/y" are refused although safe, as on main. That is deliberate.

Merge with main

This branch merged #904's slug rename. The record it resolves had a rename/rename conflict: main shortened its slug in open/ while this branch moved it to resolved/. It is resolved by hand into resolved/ at the short slug. record-slug-rename -apply then found nothing to rename.

Review

A Fable review of the three-shape fix confirmed each shape against the parent. It went silent partway through probing, so I finished that probe on a scratch copy. That probe found the split-default regression.

A second Fable review of the split fix returned FIX-FIRST on the substitution case. That fix is in 8d3cf67, with its tests watched fail first.

Resolves: iss-2610100938485695
Refs: iss-2610110025455728
Refs: iss-2610091942156774

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

REPPL added 7 commits October 10, 2026 20:03
The rm-unguarded-variable-path entry misjudged three shapes its review found.

Inside a double-quoted shell string the successor's own rewrite was refused.
The string's re-read writes ${X:?} out as the ${X} it prints, which alone can
be empty, so sh -c "rm -rf ${X:?}/y" read as the shape its guard rules out.
The tokenizer now keeps each word holding a guarded value, and the payload
re-read takes its lead from the string written with each such value as its
guard, asked only where the plain reading leads. A site that can also print
the empty text keeps its old spelling, so ${X:-}/y still refuses, and the
arg_values compare reads the same spelling as before.

A guard nested in a default was not read, so "${VAR:-${OTHER:?}}"/y and
"${VAR:-${OTHER:-/tmp}}"/y refused. The guarded values of an expansion are now
every name a :?, :- or := form guards anywhere in it, less any name it also
writes another way. That also closes "${VAR:-${VAR}}"/y, which the old exact
text skip allowed although it prints VAR's empty value.

A variable followed by a command substitution before the slash was not
followed, so rm -rf $VAR$(true)/x was allowed. The site is now followed past
substitution output, backticks included. Arithmetic expansion always prints a
number and stays allowed.

Resolves: iss-2610100938485695
Assisted-by: Claude:claude-opus-5-5
…edges

# Conflicts:
#	.abcd/work/issues/open/iss-2610100938485695-the-rm-unguarded-variable-path-guard-entry-misjudges-three.md
#	.abcd/work/issues/open/iss-2610100938485695-the-rm-unguarded-variable-path-guard.md
#	.abcd/work/issues/resolved/iss-2610100938485695-the-rm-unguarded-variable-path-guard-entry-misjudges-three.md
An unquoted default holding a blank leaves the `/` opening a field of its
own: `rm -rf ${X:- }/y` with X unset is `rm -rf /y` in bash, sh and dash,
and `${X:-a }/y` is `a` and `/y`. The entry now reads only the text after
an unquoted site's last blank, so both refuse; a quoted, escaped or ANSI-C
blank stays text and stays allowed.

Inside a double-quoted shell string the guarded rewrite hid the same
defaults from the re-read: `sh -c "rm -rf ${X:- }/y"` and
`sh -c "rm -rf ${X:-\"\"}/y"` were written as `${X:?}` and allowed,
although the string's shell splits the blank and takes the quotes off. A
site is now written as its guard only when no text it prints can be emptied
there; otherwise it keeps `${X}`, which refuses as main did.

Refs: iss-2610100938485695

Assisted-by: Claude:claude-opus-5-5
Refs: iss-2610110025455728

Assisted-by: Claude:claude-opus-5-5
A default the enclosing shell writes as an escaped substitution is run by
the string's shell, which can print nothing: `sh -c "rm -rf ${X:-\$(true)}/y"`
hands it `rm -rf $(true)/y`. The text read as one that cannot be emptied, so
the site was written out as `${X:?}` and allowed, where main refused it. A
`$` or backquote that opens nothing the guard follows is now read as able to
print nothing.

A default written with a blank inside the string's own quotes
(`sh -c "rm -rf \"${X:-my dir}\"/y"`) is still refused, as main refuses it:
the site cannot see the quotes the string's shell will read.

Refs: iss-2610100938485695

Assisted-by: Claude:claude-opus-5-5
Refs: iss-2610110025455728

Assisted-by: Claude:claude-opus-5-5
emptiedInside reads whether a default's text opens with a backquote, a
command substitution the shell string's own shell runs. That is shell
grammar, not markdown, as the guard's other allowlisted files are.

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 1b0c756 Oct 11, 2026
13 checks passed
@REPPL
REPPL deleted the fix/guard-rm-varpath-edges branch October 11, 2026 03:12
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