mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
fix(agents): never prove a command carrying a substitution or relative cd
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,15 @@ defer() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# A command substitution is concatenated into the word it sits in, and splitting the command on
|
||||
# its opener cuts that word in half: `/tmp/a/`printf ../../etc`` would be proved as `/tmp/a/`
|
||||
# and the traversal validated as an unrelated segment. Nothing here can evaluate the
|
||||
# substitution, so a command carrying one is never proved — heredoc bodies excepted, since
|
||||
# those are data the split already dropped.
|
||||
case "$(strip_heredoc_bodies "$cmd")" in
|
||||
*'$('* | *'`'*) defer "command substitution in the command line" ;;
|
||||
esac
|
||||
|
||||
# Prints the root class of a charset-safe path token, resolving a relative one against the
|
||||
# tracked working directory. Fails, printing nothing, when the token is unsafe to reason about
|
||||
# or lands outside every root.
|
||||
|
||||
@@ -45,6 +45,15 @@ defer() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# A command substitution is concatenated into the word it sits in, and splitting the command on
|
||||
# its opener cuts that word in half: `/tmp/a/`printf ../../etc`` would be proved as `/tmp/a/`
|
||||
# and the traversal validated as an unrelated segment. Nothing here can evaluate the
|
||||
# substitution, so a command carrying one is never proved — heredoc bodies excepted, since
|
||||
# those are data the split already dropped.
|
||||
case "$(strip_heredoc_bodies "$cmd")" in
|
||||
*'$('* | *'`'*) defer "command substitution in the command line" ;;
|
||||
esac
|
||||
|
||||
# Proves one `rm` segment, whose tokens are in SEG_TOKS with `rm` at index 0, resolving relative
|
||||
# operands against $seg_cwd. Returns only once every operand is an auto-allowable target;
|
||||
# anything it cannot prove defers instead.
|
||||
|
||||
@@ -168,7 +168,7 @@ segment_tokens() {
|
||||
|
||||
# Prints the directory a `cd` lands in, given the current one ($1) and the tokens after the
|
||||
# `cd` ($2...). Fails, printing nothing, when the destination cannot be resolved — a variable,
|
||||
# `-`, an option, no operand at all (`cd` alone is $HOME), or more than one.
|
||||
# `-`, an option, a relative path, no operand at all (`cd` alone is $HOME), or more than one.
|
||||
#
|
||||
# Resolving says nothing about whether the `cd` will SUCCEED: the destination may not exist, and
|
||||
# `;` runs the next command anyway, leaving it in the directory it started in. So a caller may
|
||||
@@ -180,12 +180,10 @@ apply_cd() {
|
||||
[ "$#" -eq 1 ] || return 1
|
||||
t="$1"
|
||||
[ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/-')" ] && return 1
|
||||
case "$t" in -*) return 1 ;; esac
|
||||
case "$t" in
|
||||
/*) realpath -m -- "$t" 2>/dev/null ;;
|
||||
*) [ -n "$cwd" ] || return 1
|
||||
realpath -m -- "$cwd/$t" 2>/dev/null ;;
|
||||
esac
|
||||
# Absolute only. A relative destination is not `$cwd/$t`: the shell searches $CDPATH first,
|
||||
# so `cd ssh` may land in /etc/ssh, and this cannot see the caller's $CDPATH to rule it out.
|
||||
case "$t" in /*) ;; *) return 1 ;; esac
|
||||
realpath -m -- "$t" 2>/dev/null
|
||||
}
|
||||
|
||||
# Prints the class of a canonical path and returns 0: `tmp` for one strictly under /tmp, `repo`
|
||||
|
||||
@@ -132,6 +132,11 @@ run $G ask "cd /tmp/a && cd /tmp/b && rm -rf sub"
|
||||
run $G ask "rm -rf /tmp/clone/.git" # history is never in a class
|
||||
run $G ask "rm -rf /tmp/scratch/id_rsa.key"
|
||||
run $G none "cd /tmp >$OUT; rm -f /tmp/a"
|
||||
# A substitution is concatenated into its word, so splitting on it would prove only the literal
|
||||
# half; a relative `cd` is not $cwd/$t either, since the shell searches $CDPATH first.
|
||||
run $G ask 'rm -rf /tmp/a/`printf ../../etc`'
|
||||
run $G ask 'rm -rf /tmp/a/$(printf ../../etc)'
|
||||
run $G ask "cd ssh && rm -rf moduli"
|
||||
|
||||
echo
|
||||
echo "== allow-fileops-in-tmp.sh =="
|
||||
@@ -180,6 +185,7 @@ run $A ask "cd /tmp/does-not-exist; mv .claude/settings.json settings.bak"
|
||||
# what a later operand resolves to — neither may ride along on an allow.
|
||||
run $A none "cd /tmp >$OUT; mv /tmp/a /tmp/b"
|
||||
run $A none "cp -r /tmp/tree /tmp/live; cp /tmp/payload /tmp/live/link"
|
||||
run $A ask 'mv /tmp/a/`printf ../../etc/x` /tmp/b'
|
||||
|
||||
echo
|
||||
[ "$fails" = 0 ] && echo "ALL PASS" || { echo "$fails FAILURES"; exit 1; }
|
||||
|
||||
Reference in New Issue
Block a user