Skip to content

fix(task): resolve sandbox allow_read/allow_write against task dir#9428

Merged
jdx merged 3 commits intomainfrom
claude/sad-bassi-9443fc
Apr 27, 2026
Merged

fix(task): resolve sandbox allow_read/allow_write against task dir#9428
jdx merged 3 commits intomainfrom
claude/sad-bassi-9443fc

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 27, 2026

Summary

  • Task-level allow_read / allow_write paths in mise.toml were resolved against the shell's pwd rather than the task's effective working directory. With dir = "../bar" and allow_read = ["."], mise allowed reads in the caller's directory but the task itself ran in bar/, so reads got blocked.
  • Pre-resolve task-level relative paths against task.dir(config) (falling back to config_root) before merging with CLI overrides. CLI flags (mise run --allow-read=… / mise x --allow-read=…) continue to resolve against shell cwd since those come from the user's shell.
  • Added an e2e regression test under e2e/sandbox/ and a one-line note in docs/sandboxing.md clarifying the semantics.

Fixes #9423

Test plan

  • mise run lint
  • mise run test:unit
  • mise run test:e2e test_sandbox_task_dir test_sandbox_task test_sandbox_deny_read test_sandbox_deny_write test_sandbox_deny_env test_sandbox_deny_all test_sandbox_deny_net
  • Confirmed the new e2e test fails on the unfixed code and passes after the fix
  • Manual reproduction from the discussion: dir = "../bar" + allow_read = ["."] now works without duplicating the dir

🤖 Generated with Claude Code


Note

Medium Risk
Changes how sandbox filesystem allowlists are computed for tasks, which can affect which paths are permitted/denied at runtime. Risk is moderate because it alters security-relevant path resolution logic but is narrowly scoped and covered by a new e2e regression test.

Overview
Fixes task sandboxing so task-level relative allow_read/allow_write entries are pre-resolved against the task’s effective working directory (task.dir(...)) before merging with CLI sandbox overrides, ensuring entries like "." refer to where the task actually runs.

Adds an e2e regression test (e2e/sandbox/test_sandbox_task_dir) that runs a task with dir = "../bar" and verifies allow_write = ["."] correctly permits writes in the task directory outside the default always-writable paths.

Reviewed by Cursor Bugbot for commit dac33e0. Bugbot is set up for automated code reviews on this repo. Configure here.

Task-level relative `allow_read` / `allow_write` paths were resolved against
the shell's current working directory instead of the task's effective
directory, so `dir = "../bar"` + `allow_read = ["."]` allowed reads in the
caller's pwd rather than `bar/`. Pre-resolve task-level relative paths
against `task.dir(config)` (falling back to `config_root`) before merging
with CLI overrides; CLI flags continue to resolve against shell cwd.

Fixes #9423

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR fixes a sandbox path-resolution bug where task-level allow_read/allow_write relative paths were being resolved against the invoking shell's cwd rather than the task's effective dir, causing permission mismatches when dir pointed to a different location. The fix pre-resolves task-declared paths against task.dir(config) inside a new async build_sandbox_for_task, while CLI-supplied paths continue to resolve against cwd via the existing resolve_paths() call. A /var/tmp-based e2e regression test and a docs note are included.

Confidence Score: 5/5

Safe to merge — the fix is correctly scoped and well-tested.

No P0 or P1 issues found. The path-resolution logic handles all three cases correctly (absolute path passthrough, relative joined against task_base, and the rare None-base fallback that degrades to the previous cwd behaviour). The allow_net/allow_env fields are intentionally untouched since they hold hostnames and env-var names, not filesystem paths. The e2e test covers the exact regression scenario.

No files require special attention.

Important Files Changed

Filename Overview
src/task/task_executor.rs Converts build_sandbox_for_task to async, pre-resolving task-level allow_read/allow_write relative paths against the task's effective working directory before resolve_paths() canonicalises them against cwd; logic is correct and the None fallback is handled cleanly.
e2e/sandbox/test_sandbox_task_dir New regression test under /var/tmp (outside sandbox always-writable paths) that verifies allow_write = ["."] + dir = "../bar" allows writes in the task dir; covers the exact bug from the linked discussion.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller (shell cwd)
    participant TE as TaskExecutor
    participant Task
    participant SB as SandboxConfig

    Caller->>TE: run_task_sched(task, config)
    TE->>TE: build_sandbox_for_task(task, config)
    TE->>Task: task.dir(config)
    Task-->>TE: task_base (e.g. /project/bar)

    note over TE: resolve_task_path closure joins relative<br/>allow_read/write against task_base

    TE->>SB: SandboxConfig { allow_read: task paths (pre-resolved) + CLI paths (raw) }
    TE->>SB: resolve_paths() — canonicalise all, resolve remaining relatives against cwd
    SB-->>TE: fully-resolved SandboxConfig
    TE-->>Caller: sandbox enforced on task execution
Loading

Reviews (3): Last reviewed commit: "docs(sandboxing): drop redundant note on..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request ensures that relative paths in task-level allow_read and allow_write sandbox settings are resolved against the task's working directory instead of the shell's current working directory. The changes include documentation updates, a new regression test, and modifications to the sandbox configuration logic in task_executor.rs. A review comment points out a redundancy in the path resolution logic where a fallback is already handled by an existing method.

Comment thread src/task/task_executor.rs Outdated
task: &Task,
config: &Arc<Config>,
) -> Result<SandboxConfig> {
let task_base = task.dir(config).await?.or_else(|| task.config_root.clone());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The .or_else(|| task.config_root.clone()) call is redundant here because task.dir(config) already falls back to task.config_root if the task's dir is not specified. You can simplify this to just task.dir(config).await?.

Suggested change
let task_base = task.dir(config).await?.or_else(|| task.config_root.clone());
let task_base = task.dir(config).await?;

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f3b4dd5. Configure here.

Comment thread src/task/task_executor.rs Outdated
Comment thread src/task/task_executor.rs
jdx and others added 2 commits April 27, 2026 10:11
…nding

Address PR feedback:
- task.dir(config) already returns config_root when dir is unset, so the
  outer .or_else fallback was dead code.
- Rename the local SandboxConfig binding from `config` to `sandbox` so it
  no longer shadows the `config: &Arc<Config>` parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The behavior is obvious from how `dir` itself works.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.24 x -- echo 22.6 ± 0.6 21.5 24.7 1.00
mise x -- echo 23.0 ± 1.0 21.7 30.4 1.02 ± 0.05

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.24 env 22.4 ± 1.1 20.8 28.4 1.00
mise env 23.4 ± 0.6 21.6 25.2 1.04 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.24 hook-env 23.2 ± 0.7 21.6 25.6 1.01 ± 0.04
mise hook-env 23.0 ± 0.6 22.0 25.2 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.24 ls 23.0 ± 0.7 22.1 29.1 1.00
mise ls 23.4 ± 0.6 22.4 25.8 1.02 ± 0.04

xtasks/test/perf

Command mise-2026.4.24 mise Variance
install (cached) 155ms 159ms -2%
ls (cached) 80ms 81ms -1%
bin-paths (cached) 82ms 84ms -2%
task-ls (cached) 800ms 805ms +0%

@jdx jdx merged commit afd318f into main Apr 27, 2026
36 checks passed
@jdx jdx deleted the claude/sad-bassi-9443fc branch April 27, 2026 17:34
mise-en-dev added a commit that referenced this pull request Apr 28, 2026
### 🚀 Features

- **(task)** add --name-only flag to mise tasks ls by @jdx in
[#9435](#9435)

### 🐛 Bug Fixes

- **(Dockerfile)** install copr-cli via dnf for better dependency
management by @bestagi in [#9421](#9421)
- **(aqua)** drop empty-releases fallback to tags by @jdx in
[#9443](#9443)
- **(docs)** fix theme flicker on docs by @vhespanha in
[#9427](#9427)
- **(lockfile)** update global lockfile on upgrade by @jdx in
[#9442](#9442)
- **(ls-remote)** omit rolling/prerelease from JSON when false by @jdx
in [#9439](#9439)
- **(task)** support usage refs in dependency template tags by @jdx in
[#9424](#9424)
- **(task)** populate usage.cmd for subcommand-only tasks; share
make_usage_ctx by @jdx in [#9431](#9431)
- **(task)** resolve sandbox allow_read/allow_write against task dir by
@jdx in [#9428](#9428)

### 📚 Documentation

- **(site)** add self-hosted page tracker via Cloudflare Worker, drop
GoatCounter by @jdx in [#9430](#9430)

### New Contributors

- @vhespanha made their first contribution in
[#9427](#9427)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant