Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions agent_tool/shell/README.mbt.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ workspace root, respecting `moon -C <dir>` and explicit cwd changes such as
`cd ./dir && moon ...`. Bare relative `cd dir` is skipped because `CDPATH` can
change which directory the shell enters. This covers commands such as
`moon add`, `moon remove`, `moon update`, `moon fmt`, `moon info`,
`moon test --update`, and `moon ide rename ... --apply`. Read-only variants
`moon test -u`/`moon test --update`, and `moon ide rename ... --apply`. Read-only variants
such as dry-run commands and `moon fmt --check` are left alone, as are Moon
invocations with per-command environment overrides such as `FOO=bar moon` or
`env FOO=bar moon`. Follow-up checks are skipped when `timeout_ms` is set and
Expand Down Expand Up @@ -96,12 +96,15 @@ source paths are indirect, as in `while read f; do sed -i ... "$f"; done`.
Too-complex commands with visible MoonBit source creation or tree transfer
markers are also rejected before execution.

Narrowly recognized Moon commands that are expected to write source or package
metadata run outside the source-write sandbox: `moon fmt`, `moon info`,
`moon add`, `moon remove`, `moon update`, `moon test --update`, and
`moon ide rename ... --apply`. Compounds are conservative; `moon fmt &&
moon check` is trusted, while a broad script or source rewrite through shell is
not.
Recognized Moon commands that can run pre-build hooks or intentionally update
source and package metadata run outside the source-write sandbox. This includes
`moon check`, `moon build`, `moon test`, package-based `moon run`, `moon fmt`,
`moon info`, `moon add`, `moon remove`, `moon update`, `moon test -u`/`moon test
--update`, and `moon ide rename ... --apply`. Compounds are conservative; `moon
fmt && moon check` is trusted. A trusted writer may pipe output through the
read-only `head` limiter, so commands such as `moon test -u 2>&1 | head -40` can
update snapshots while bounding output. Other pipeline consumers, broad scripts,
and source rewrites through shell remain sandboxed.

Recognized first-party Git porcelain composes outside the source-write sandbox.
This lets ordinary workflows such as `git fetch && git rebase`, `git pull
Expand Down
1 change: 1 addition & 0 deletions agent_tool/shell/moon_auto_check.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ test "detect moon commands that should trigger follow-up check" {
),
)
assert_true(should_auto_check_moon_command("moon test --update"))
assert_true(should_auto_check_moon_command("moon test -u"))
assert_true(should_auto_check_moon_command("command moon fmt"))
assert_true(should_auto_check_moon_command("command -- moon fmt"))
assert_true(should_auto_check_moon_command("command -p moon fmt"))
Expand Down
7 changes: 7 additions & 0 deletions agent_tool/shell/review_guard.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ test "read-only guard blocks mutating moon anywhere; allows read-only commands"
scratch_dir=None,
),
)
assert_true(
review_read_only_blocks(
@shell_parse.parse_for_policy("moon test -u; true"),
base_cwd=".",
scratch_dir=None,
),
)
assert_true(
review_read_only_blocks(
@shell_parse.parse_for_policy("FOO=bar moon fmt"),
Expand Down
30 changes: 27 additions & 3 deletions agent_tool/shell/sandbox.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,17 @@ fn commands_are_trusted_source_writes(
) -> Bool {
let mut has_trusted_write = false
for index, command in commands {
if command.separator_before is Some("|" | "||") {
return false
let command_class = trusted_command_class(command, commands, index)
match command.separator_before {
Some("||") => return false
// A trusted source-writing command may feed a read-only output consumer
// such as `head`. Never trust a writer after a pipe: that would make the
// whole shell run unsandboxed for a command shape that is not needed for
// validation output limiting.
Some("|") if !(command_class is TrustedCommandRead) => return false
_ => ()
}
match trusted_command_class(command, commands, index) {
match command_class {
TrustedCommandWrite => has_trusted_write = true
TrustedCommandNeutral | TrustedCommandRead => ()
UntrustedCommand => return false
Expand Down Expand Up @@ -220,12 +227,29 @@ fn trusted_command_class(
}
"moon" => trusted_moon_command_class(command, command_index)
"git" => trusted_git_command_class(command, command_index)
"head" => trusted_head_command_class(command, command_index)
_ => UntrustedCommand
}
_ => UntrustedCommand
}
}

///|
/// `head` is a read-only output limiter used to keep compiler and test output
/// bounded (`moon test ... 2>&1 | head -30`). Trust only the direct command
/// without environment wrappers; source-path redirects are rejected before
/// this classifier runs.
fn trusted_head_command_class(
command : @shell_parse.SimpleCommand,
head_index : Int,
) -> TrustedCommandClass {
if head_index == 0 && !command_has_custom_environment(command) {
TrustedCommandRead
} else {
UntrustedCommand
}
}

///|
/// Classify a `git` command for the trusted-source-write path. Recognized
/// first-party porcelain composes by default, while aliases, plumbing, external
Expand Down
23 changes: 23 additions & 0 deletions agent_tool/shell/sandbox_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ test "sandbox trusts only narrow moon source-writing commands" {
assert_true(mode_for("moon fmt >/dev/null") is TrustedSourceWrite)
assert_true(mode_for("moon info > /tmp/log") is TrustedSourceWrite)
assert_true(mode_for("moon test --update 2>/tmp/err") is TrustedSourceWrite)
assert_true(mode_for("moon test -u 2>/tmp/err") is TrustedSourceWrite)
assert_true(mode_for("moon fmt > out.log") is TrustedSourceWrite)
assert_true(mode_for("moon check") is TrustedSourceWrite)
assert_true(mode_for("moon build") is TrustedSourceWrite)
assert_true(mode_for("moon test") is TrustedSourceWrite)
assert_true(
mode_for("moon test cfront/build -u 2>&1 | head -30") is TrustedSourceWrite,
)
assert_true(
mode_for("moon test cfront/build --update 2>&1 | head -n 40")
is TrustedSourceWrite,
)
assert_true(mode_for("moon run cmd/main") is TrustedSourceWrite)
assert_true(mode_for("moon run cmd/main -- --help") is TrustedSourceWrite)
assert_true(mode_for("moon run cmd/main -- -u") is TrustedSourceWrite)
Expand Down Expand Up @@ -48,6 +56,21 @@ test "sandbox trusts only narrow moon source-writing commands" {
assert_true(mode_for("sed -i '' 's/a/b/' *.mbt") is SourceReadOnly)
}

///|
test "sandbox trusts only read-only consumers after moon output pipes" {
assert_true(mode_for("moon test 2>&1 | head -30") is TrustedSourceWrite)
assert_true(
mode_for("moon test -u 2>&1 | head -40 | head -20") is TrustedSourceWrite,
)
for
cmd in [
"moon test -u | tee snapshots.txt", "moon test -u | sh", "moon test -u | moon fmt",
"env LIMIT=30 head -30 | moon test -u",
] {
assert_true(mode_for(cmd) is SourceReadOnly)
}
}

///|
async test "sandbox preblocks source-writing script snippets" {
let root = "/workspace"
Expand Down
Loading