framework: make tool discoverability, workspace placement and model tiering mechanical #1174
@@ -35,6 +35,22 @@ FIXTURES="$TMP/fixtures.tsv"
|
||||
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'
|
||||
# Path words use the same quote/escape state machine as names, but preserve
|
||||
# substitutions so HOME remains visible. Quotes do not split the path word.
|
||||
printf '2\t{"tool_input":{"command":"git clone x \\"$HOME\\"/wt"}}\ta closing quote between HOME and slash does not hide the path\n'
|
||||
printf '2\t{"tool_input":{"command":"git clone x ${HOME}/wt"}}\tthe braced HOME spelling is the same home path\n'
|
||||
printf '2\t{"tool_input":{"command":"git clone x \\"${HOME}\\"/wt"}}\tbraced HOME may also end a quoted span before the slash\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x $HOME_BACKUP/wt"}}\ta longer HOME-prefixed variable is a different path\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x $HOMEBREW/wt"}}\tHOMEBREW is not HOME either\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x $HOME}/wt"}}\ta closing brace without an opening brace is a literal suffix\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x ${HOME/wt"}}\tan opening brace without a close is not a HOME expansion\n'
|
||||
# Quote removal must not create an expansion the shell never performs.
|
||||
printf '0\t{"tool_input":{"command":"git clone x '"'"'$HOME'"'"'/wt"}}\tsingle-quoted HOME is a literal directory name\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x \\\\$HOME/wt"}}\tan escaped dollar makes HOME literal outside quotes\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x \\"\\\\$HOME\\"/wt"}}\tan escaped dollar makes HOME literal inside double quotes\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x \\"~/wt\\""}}\ttilde does not expand inside double quotes\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x '"'"'~/wt'"'"'"}}\ttilde does not expand inside single quotes\n'
|
||||
printf '0\t{"tool_input":{"command":"git clone x \\\\~/wt"}}\tan escaped tilde is literal too\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
|
||||
@@ -438,10 +454,12 @@ home_case() {
|
||||
local why="$1" want="$2" homeval="$3" cmd="$4" needle="${5:-}"
|
||||
local out got
|
||||
n=$((n + 1))
|
||||
local payload
|
||||
payload="$(jq -nc --arg command "$cmd" '{tool_input:{command:$command}}')"
|
||||
if [ "$homeval" = "@unset" ]; then
|
||||
out="$(printf '%s' "{\"tool_input\":{\"command\":\"$cmd\"}}" | env -u HOME "$GUARD" 2>&1)"
|
||||
out="$(printf '%s' "$payload" | env -u HOME "$GUARD" 2>&1)"
|
||||
else
|
||||
out="$(printf '%s' "{\"tool_input\":{\"command\":\"$cmd\"}}" | env HOME="$homeval" "$GUARD" 2>&1)"
|
||||
out="$(printf '%s' "$payload" | env HOME="$homeval" "$GUARD" 2>&1)"
|
||||
fi
|
||||
got=$?
|
||||
if [ "$got" != "$want" ]; then
|
||||
@@ -467,6 +485,8 @@ home_case 'a usable HOME still allows a checkout onto a work filesystem' \
|
||||
0 '/home/tester' 'git clone https://example.invalid/x /src/wt'
|
||||
home_case 'a usable HOME still catches the literal path' \
|
||||
2 '/home/tester' 'git clone https://example.invalid/x /home/tester/wt' 'checks a repository out under'
|
||||
home_case 'a quoted literal HOME segment remains contiguous with the suffix' \
|
||||
2 '/home/tester' 'git clone https://example.invalid/x "/home/tester"/wt' 'checks a repository out under'
|
||||
home_case 'and the unexpanded $HOME spelling, which needs no resolution at all' \
|
||||
2 '/home/tester' 'git worktree add $HOME/wt topic' 'checks a repository out under'
|
||||
# The fail-closed arm is scoped to checkouts. If it were not, a seat with no
|
||||
|
||||
@@ -93,16 +93,29 @@ 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.
|
||||
normalize_command_names() {
|
||||
awk '
|
||||
BEGIN { state = "outside"; out = "" }
|
||||
normalize_command_words() {
|
||||
local flatten_substitutions="${1:-1}"
|
||||
local protect_path_literals="${2:-0}"
|
||||
awk -v flatten_substitutions="$flatten_substitutions" \
|
||||
-v protect_path_literals="$protect_path_literals" '
|
||||
BEGIN {
|
||||
state = "outside"; out = ""; word_start = 1
|
||||
literal_dollar = sprintf("%c", 28)
|
||||
literal_tilde = sprintf("%c", 29)
|
||||
}
|
||||
{
|
||||
if (NR > 1) out = out "\n"
|
||||
if (NR > 1) { out = out "\n"; word_start = 1 }
|
||||
for (i = 1; i <= length($0); i++) {
|
||||
c = substr($0, i, 1)
|
||||
if (state == "single") {
|
||||
if (c == "\047") state = "outside"
|
||||
else out = out c
|
||||
if (c == "\047") {
|
||||
state = "outside"
|
||||
} else {
|
||||
if (protect_path_literals && c == "$") out = out literal_dollar
|
||||
else if (protect_path_literals && c == "~") out = out literal_tilde
|
||||
else out = out c
|
||||
word_start = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (state == "double") {
|
||||
@@ -114,33 +127,58 @@ normalize_command_names() {
|
||||
} else {
|
||||
nextc = substr($0, i + 1, 1)
|
||||
if (nextc == "$" || nextc == "`" || nextc == "\"" || nextc == "\\") {
|
||||
out = out nextc
|
||||
if (protect_path_literals && nextc == "$") out = out literal_dollar
|
||||
else out = out nextc
|
||||
} else {
|
||||
out = out c nextc
|
||||
out = out c
|
||||
if (protect_path_literals && nextc == "~") out = out literal_tilde
|
||||
else out = out nextc
|
||||
}
|
||||
word_start = 0
|
||||
i++
|
||||
}
|
||||
} else if (c != "$" && c != "(" && c != ")" && c != "`") {
|
||||
out = out c
|
||||
} else if (!flatten_substitutions || (c != "$" && c != "(" && c != ")" && c != "`")) {
|
||||
if (protect_path_literals && c == "~") out = out literal_tilde
|
||||
else out = out c
|
||||
word_start = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (c == "\\") {
|
||||
if (i == length($0)) out = out c
|
||||
else { out = out substr($0, i + 1, 1); i++ }
|
||||
if (i == length($0)) {
|
||||
out = out c
|
||||
} else {
|
||||
nextc = substr($0, i + 1, 1)
|
||||
if (protect_path_literals && nextc == "$") out = out literal_dollar
|
||||
else if (protect_path_literals && nextc == "~") out = out literal_tilde
|
||||
else out = out nextc
|
||||
i++
|
||||
}
|
||||
word_start = 0
|
||||
} else if (c == "\047") {
|
||||
state = "single"
|
||||
} else if (c == "\"") {
|
||||
state = "double"
|
||||
} else if (c != "$" && c != "(" && c != ")" && c != "`") {
|
||||
out = out c
|
||||
} else if (!flatten_substitutions || (c != "$" && c != "(" && c != ")" && c != "`")) {
|
||||
if (protect_path_literals && c == "~" && !word_start) out = out literal_tilde
|
||||
else out = out c
|
||||
if (c ~ /[[:space:]|;&]/) word_start = 1
|
||||
else word_start = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
END { printf "%s", out }
|
||||
'
|
||||
}
|
||||
CMD_NAMES="$(printf '%s' "$CMD" | normalize_command_names)"
|
||||
CMD_NAMES="$(printf '%s' "$CMD" | normalize_command_words 1 0)"
|
||||
# Paths need the same quote and escape handling, but not name-mode substitution
|
||||
# flattening: an expansion-capable `$HOME` spelling must remain visible to the
|
||||
# checkout check. Shell-literal dollar/tilde characters (single-quoted, escaped,
|
||||
# or a quoted tilde) become internal nonmatching markers; otherwise quote removal
|
||||
# would create a HOME spelling the shell never expands. A path assembled from a
|
||||
# different variable or command substitution is absent from the literal text and
|
||||
# remains outside a text guard's visibility.
|
||||
CMD_PATHS="$(printf '%s' "$CMD" | normalize_command_words 0 1)"
|
||||
|
||||
# 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
|
||||
@@ -243,7 +281,10 @@ W="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# left unresolved BLOCKS rather than clears — a guard may not clear a question it
|
||||
# was unable to ask. The blast radius of that fail-closed arm is exactly one
|
||||
# command shape (clone / worktree add), not the session.
|
||||
home_re='~|\$HOME'
|
||||
# Braces are a pair, not independently optional: `$HOME}` expands HOME and
|
||||
# appends a literal `}`, while `${HOME` is not a valid expansion. Treating either
|
||||
# as `${HOME}` would create a home path the shell never resolves.
|
||||
home_re='~|\$HOME|\$\{HOME\}'
|
||||
if [ "$home_known" -eq 1 ]; then
|
||||
home_re="$home_re|$(printf '%s' "$HOME_DIR" | sed 's/[][\.*^$+?(){}|]/\\&/g')"
|
||||
fi
|
||||
@@ -267,7 +308,7 @@ EOF
|
||||
exit 2
|
||||
fi
|
||||
# Any argument that resolves under $HOME and is not under a work filesystem.
|
||||
if printf '%s' "$CMD" | grep -Eq "(^|[[:space:]=\"'])($home_re)/"; then
|
||||
if printf '%s' "$CMD_PATHS" | grep -Eq "(^|[[:space:]=])($home_re)/"; then
|
||||
cat <<EOF
|
||||
BLOCKED: this checks a repository out under \$HOME.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user