framework: make tool discoverability, workspace placement and model tiering mechanical #1174

Merged
Mos merged 24 commits from feat/workspace-hygiene-tool-enforcement into main 2026-08-13 14:21:23 +00:00
2 changed files with 88 additions and 11 deletions
Showing only changes of commit 3d0a882a63 - Show all commits
@@ -32,7 +32,17 @@ FIXTURES="$TMP/fixtures.tsv"
{
printf '2\t{"tool_input":{"command":"git clone https://example.invalid/x ~/wt"}}\tcheckout into $HOME is refused\n'
printf '2\t{"tool_input":{"command":"git worktree add ~/wt topic"}}\tworktree into $HOME is refused\n'
printf '2\t{"tool_input":{"command":"g\\"it\\" clone https://example.invalid/x $HOME/wt"}}\ta double quote inside git does not hide a checkout\n'
printf '2\t{"tool_input":{"command":"g'"'"'it'"'"' clone https://example.invalid/x $HOME/wt"}}\ta single quote inside git does not hide a checkout\n'
printf '2\t{"tool_input":{"command":"g\\\\it clone https://example.invalid/x $HOME/wt"}}\tan unquoted escape inside git does not hide a checkout\n'
printf '0\t{"tool_input":{"command":"git clone https://example.invalid/x /src/wt"}}\tcheckout onto a work filesystem is fine\n'
# Routing this arm through the shared name site also repaired an over-block it
# had carried from the start: the old whole-command regex found `git` INSIDE a
# longer word, so these two were refused at every head before this commit.
# Same class as mycurl and curl-wrapper, and refusing them is how a guard gets
# routed around instead of repaired.
printf '0\t{"tool_input":{"command":"mygit clone https://example.invalid/x $HOME/wt"}}\tmygit is a different program and its checkout is not ours\n'
printf '0\t{"tool_input":{"command":"gitfoo clone https://example.invalid/x $HOME/wt"}}\tthe name has to end where git ends\n'
printf '0\t{"tool_input":{"command":"curl -s -X GET https://git.example.invalid/api/v1/repos/a/b/pulls/1"}}\treads are never blocked\n'
printf '2\t{"tool_input":{"command":"curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/pulls/1/reviews"}}\treview write has a wrapper\n'
printf '2\t{"tool_input":{"command":"curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/pulls/1/merge"}}\tmerge write has a wrapper\n'
@@ -323,6 +333,22 @@ FIXTURES="$TMP/fixtures.tsv"
printf '2\t{"tool_input":{"command":"cu\\"rl\\" --config /tmp/provider-write.cfg"}}\ta quote inside the word does not make it another program\t--config/-K\n'
printf '2\t{"tool_input":{"command":"cu'"'"'rl'"'"' --config /tmp/provider-write.cfg"}}\tand a single quote inside it is the same word again\t--config/-K\n'
printf '2\t{"tool_input":{"command":"/usr/bin/cu\\\\rl --config /tmp/provider-write.cfg"}}\tescaping is ordinary word formation, not a disguise\t--config/-K\n'
# A backslash is NOT uniformly removed. It is literal inside single quotes,
# and inside double quotes when it precedes anything other than $, `, ",
# backslash, or newline. These spell a different program and must stay allowed.
printf '0\t{"tool_input":{"command":"'"'"'cu\\\\rl'"'"' --config /tmp/provider-write.cfg"}}\ta backslash inside single quotes remains literal\n'
printf '0\t{"tool_input":{"command":"\\"cu\\\\rl\\" --config /tmp/provider-write.cfg"}}\ta backslash before r inside double quotes remains literal\n'
printf '0\t{"tool_input":{"command":"'"'"'g\\\\it'"'"' clone https://example.invalid/x $HOME/wt"}}\ta literal backslash in a single-quoted non-git name is not a checkout\n'
printf '0\t{"tool_input":{"command":"\\"g\\\\it\\" clone https://example.invalid/x $HOME/wt"}}\ta literal backslash in a double-quoted non-git name is not a checkout\n'
# The other branch of the same rule: OUTSIDE quotes a backslash escapes the
# next character, so an escaped quote is a literal quote IN the name and the
# program is not curl. Held separately from the cases above because it is a
# different arm of the state machine, and an arm without a fixture is a rule
# that is not held.
printf '0\t{"tool_input":{"command":"cu\\\\\\"rl\\\\\\" --config /tmp/provider-write.cfg"}}\tan escaped quote is a literal quote in the name\n'
# Three quoted segments concatenate into ONE word. This is the shape that
# distinguishes quote removal from token separation, so it is worth its own line.
printf '2\t{"tool_input":{"command":"\\"cu\\"'"'"'r'"'"'\\"l\\" --config /tmp/provider-write.cfg"}}\tadjacent quoted segments are one word, and that word is curl\t--config/-K\n'
printf '2\t{"tool_input":{"command":"g\\"h\\" api -X POST repos/a/b/issues -f title=x"}}\tthe CLI name is a word on the same terms\n'
printf '2\t{"tool_input":{"command":"/usr/bin/g\\\\h api -X POST repos/a/b/issues -f title=x"}}\tincluding when it is escaped behind a path\n'
# The FLAG is the same recognition problem as the name, and is read the same
@@ -49,9 +49,9 @@ CMD="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null |
CMD="$(printf '%s' "$CMD" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\\\n//g')"
# A second reading of the SAME command, used by every check below that has to
# recognize a program by NAME. Quote characters, backslashes and the punctuation
# of command substitution are DELETED, so a name the shell will resolve is a word
# here regardless of how it was dressed:
# recognize a program by NAME. It applies shell word formation without executing
# expansions, so a name the shell will resolve is a word here regardless of how
# it was dressed:
#
# "/usr/bin/curl" --config /tmp/req quoted absolute path
# './curl' --config /tmp/req quoted relative path
@@ -73,10 +73,14 @@ CMD="$(printf '%s' "$CMD" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\\\n//g')"
# because the whitespace happened to land after a slash — a passing case that
# established nothing.
#
# So the characters are DELETED rather than replaced, which is what quote removal
# is, and backslashes go with them because escaping is ordinary word formation
# too. Deletion also handles substitution: `$(which curl)` becomes `which curl`,
# where the name is a word on its own.
# Quote removal and escape handling are separate operations. Outside quotes, a
# backslash escapes the next character. Inside single quotes it is literal.
# Inside double quotes it escapes only $, `, ", backslash, or newline; before
# anything else both the backslash and following character remain literal. Quote
# characters themselves are dropped without splitting the word. The existing
# substitution flattening remains: unquoted and double-quoted $, (, ), and ` are
# dropped, so `$(which curl)` becomes `which curl`, where the name is a word on
# its own.
#
# This does NOT try to be a shell. It cannot see a name that is absent from the
# text — assembled from variables, or reached through a wrapper script that
@@ -89,10 +93,57 @@ CMD="$(printf '%s' "$CMD" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\\\n//g')"
# these commands inside quotes on a Bash line is refused too. Applying that same
# rule here keeps the file coherent — the alternative is a guard where the
# payload arm treats quotes as text and the name arms treat them as armour.
CMD_NAMES="$(printf '%s' "$CMD" | tr -d '"'"'"'\`()$\\')"
normalize_command_names() {
awk '
BEGIN { state = "outside"; out = "" }
{
if (NR > 1) out = out "\n"
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (state == "single") {
if (c == "\047") state = "outside"
else out = out c
continue
}
if (state == "double") {
if (c == "\"") {
state = "outside"
} else if (c == "\\") {
if (i == length($0)) {
out = out c
} else {
nextc = substr($0, i + 1, 1)
if (nextc == "$" || nextc == "`" || nextc == "\"" || nextc == "\\") {
out = out nextc
} else {
out = out c nextc
}
i++
}
} else if (c != "$" && c != "(" && c != ")" && c != "`") {
out = out c
}
continue
}
if (c == "\\") {
if (i == length($0)) out = out c
else { out = out substr($0, i + 1, 1); i++ }
} else if (c == "\047") {
state = "single"
} else if (c == "\"") {
state = "double"
} else if (c != "$" && c != "(" && c != ")" && c != "`") {
out = out c
}
}
}
END { printf "%s", out }
'
}
CMD_NAMES="$(printf '%s' "$CMD" | normalize_command_names)"
# The one place the shape of a program NAME is written down. Both callers below
# use it, so the next fix to this class lands in a single location instead of
# The one place the shape of a program NAME is written down. Every name consumer
# below uses it, so the next fix to this class lands in a single location instead of
# being applied to whichever arm review happened to probe. The prefix 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.
@@ -197,7 +248,7 @@ if [ "$home_known" -eq 1 ]; then
home_re="$home_re|$(printf '%s' "$HOME_DIR" | sed 's/[][\.*^$+?(){}|]/\\&/g')"
fi
if printf '%s' "$CMD" | grep -Eq 'git[^|;&]*(clone|worktree[[:space:]]+add)'; then
if printf '%s' "$CMD_NAMES" | grep -Eq "${NAME_PREFIX}git[[:space:]]+[^|;&]*(clone([[:space:]]|$)|worktree[[:space:]]+add([[:space:]]|$))"; then
if [ "$home_known" -eq 0 ]; then
cat <<EOF
BLOCKED: this is a checkout, and \$HOME is unset or unusable in this shell.