mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
fix(agents): treat sibling checkouts as separate roots
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d51de5b7be
commit
9a8b01a864
@@ -17,12 +17,14 @@
|
||||
# they can only constrain the FIRST operand. `cp /tmp/x ~/.zshrc` matches a `cp /tmp/` prefix,
|
||||
# and requiring every operand is the point.
|
||||
#
|
||||
# One operation may not straddle the two roots, sources included. A copy out of a checkout into
|
||||
# /tmp would be a read-exfiltration path around the `Read(**/secrets/**)` / `Read(**/*.pem)`
|
||||
# deny rules, since the content lands where `Read(/tmp/**)` allows it to be read back; keeping
|
||||
# every operand of one operation inside a single root closes that without restating the deny
|
||||
# list here. The checkout root itself is what makes an in-repo `mv` or `chmod` auto-allowable:
|
||||
# deleting a file there has never prompted, and moving or chmod-ing one is not the graver act.
|
||||
# One operation may not straddle two roots, sources included, and a sibling checkout is a
|
||||
# different root — `path_class` names the git tree, not just its kind. A copy out of a checkout
|
||||
# into /tmp would be a read-exfiltration path around the `Read(**/secrets/**)` / `Read(**/*.pem)`
|
||||
# deny rules, since the content lands where `Read(/tmp/**)` allows it to be read back, and one
|
||||
# out of a repo the Read tool is not confined to would do the same for that repo. Keeping every
|
||||
# operand of one operation inside a single root closes both without restating those rules here.
|
||||
# The checkout root itself is what makes an in-repo `mv` or `chmod` auto-allowable: deleting a
|
||||
# file there has never prompted, and moving or chmod-ing one is not the graver act.
|
||||
#
|
||||
# Deny-by-default tokenizing, in the same spirit as guard-rm-outside-tmp.sh: every path token
|
||||
# must consist only of alphanumerics and `. _ / -`. That set contains none of the characters
|
||||
@@ -235,7 +237,7 @@ check_fileops_segment() {
|
||||
|
||||
cls=$(operand_class "$t") || defer "\`$t\` is outside /tmp and not inside a git checkout in \$HOME"
|
||||
# Every operand of one operation stays in one root: see the exfiltration note above.
|
||||
[ -n "$seen_class" ] && [ "$cls" != "$seen_class" ] && defer "\`$t\` puts this $verb across both /tmp and a checkout"
|
||||
[ -n "$seen_class" ] && [ "$cls" != "$seen_class" ] && defer "\`$t\` puts this $verb across two roots"
|
||||
seen_class="$cls"
|
||||
path_operand=1
|
||||
done
|
||||
|
||||
@@ -186,9 +186,11 @@ apply_cd() {
|
||||
realpath -m -- "$t" 2>/dev/null
|
||||
}
|
||||
|
||||
# Prints the class of a canonical path and returns 0: `tmp` for one strictly under /tmp, `repo`
|
||||
# for one strictly inside a git working tree located under $HOME. Fails, printing nothing, for
|
||||
# anything else — those are the only two roots the guards are willing to touch unprompted.
|
||||
# Prints the class of a canonical path and returns 0: `tmp` for one strictly under /tmp, or
|
||||
# `repo:<root>` for one strictly inside the git working tree at <root>, itself under $HOME.
|
||||
# Fails, printing nothing, for anything else — those are the only roots the guards are willing
|
||||
# to touch unprompted. The root is part of the class so that a caller pairing two operands can
|
||||
# tell one checkout from another: sibling repos are separate permission boundaries, not one.
|
||||
#
|
||||
# The `repo` class trades on "this is a project under version control" being lower-stakes than
|
||||
# the same act elsewhere — NOT on full recoverability: committed content is restorable via git,
|
||||
@@ -227,7 +229,7 @@ path_class() {
|
||||
if [ "$canon" = "$root" ]; then
|
||||
[ -f "$root/.git" ] || return 1
|
||||
fi
|
||||
printf 'repo'
|
||||
printf 'repo:%s' "$root"
|
||||
}
|
||||
|
||||
# 0 iff <verb> ($1) runs as a command word anywhere in <command> ($2). Mirrors how a Bash
|
||||
|
||||
@@ -186,6 +186,11 @@ run $A ask "cd /tmp/does-not-exist; mv .claude/settings.json settings.bak"
|
||||
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'
|
||||
# A sibling checkout is a different root: its files are outside what the Read tool is confined
|
||||
# to, and copying them in would hand back what that confinement withholds.
|
||||
EE="$(dirname "$CWD")/windmill-ee-private" # a sibling checkout; absent elsewhere, still not a root
|
||||
run $A ask "mv $EE/backend/x.rs $CWD/backend/x.rs"
|
||||
run $A none "cp $EE/README.md $CWD/README.copy"
|
||||
|
||||
echo
|
||||
[ "$fails" = 0 ] && echo "ALL PASS" || { echo "$fails FAILURES"; exit 1; }
|
||||
|
||||
Reference in New Issue
Block a user