Skip to content

Add sandbox.container.excluded_files to mask worktree files from containers#134

Merged
raine merged 4 commits intoraine:mainfrom
joshuavial:configurable-env-exclusion
Apr 18, 2026
Merged

Add sandbox.container.excluded_files to mask worktree files from containers#134
raine merged 4 commits intoraine:mainfrom
joshuavial:configurable-env-exclusion

Conversation

@joshuavial
Copy link
Copy Markdown
Contributor

Summary

  • New sandbox.container.excluded_files config: list of worktree-relative paths that get shadowed by a read-only /dev/null bind mount, so an agent running inside the container cannot read them (e.g. .env files). Default unset — no behavior change.
  • Paths that escape the worktree (absolute or ..) are rejected; missing files are skipped with a warning.
  • Gracefully skipped (with warning) on runtimes without file-level bind mounts (Apple Container).
  • Config key is documented in docs/guide/sandbox/container.md and discoverable via workmux config reference / workmux init.

Test plan

  • Unit tests cover default, successful masking, missing files, escape-path rejection, Apple-Container skip, and config merge (9 new tests, all passing)
  • cargo fmt --check clean
  • Full cargo test suite (946 tests) passes
  • Manually verified on a real project: inside the container the .env file shows size 0 while the host file is unchanged; non-excluded files like README.md remain readable

@joshuavial joshuavial force-pushed the configurable-env-exclusion branch from 18dd8d0 to 44c47d7 Compare April 12, 2026 11:10
joshuavial and others added 4 commits April 18, 2026 14:22
Address review feedback on the excluded_files masking feature:

- Path safety: replace stringy `contains("..")` check with a
  Component::ParentDir walk so legitimate filenames like `foo..bar`
  and `.env..local` are no longer falsely rejected while actual
  parent-dir traversal is still blocked.
- Global-only scope: a project's `.workmux.yaml` can no longer set
  `excluded_files`. Letting project config override the global list
  defeats the purpose -- a malicious repo could hand itself an empty
  list to strip user-level secret protections. Now matches the scope
  rules already used for host_commands, extra_mounts, and network.
- Apple Container: set-but-unusable is now a hard error instead of
  a silent tracing warning. The warning was written to the log file
  rather than stderr, so users would not notice that their secrets
  were still readable inside the sandbox. Fail fast instead.
- Main-worktree alias: workmux bind-mounts the main worktree for
  symlink resolution, which created an alias path through which
  a masked secret (e.g. `.env` symlinked into the main worktree)
  remained readable. The masking step now runs after both mounts
  and shadows both the current-worktree and main-worktree paths.

Tests updated and added: global-only merge semantics, hard error on
Apple Container, safe dotted names accepted, and main-worktree alias
masking under a realistic gitlink layout. Docs updated to describe
the new behavior.
Two follow-ups to excluded_files:

- Relative gitdir: `git worktree add --relative-paths` (git 2.48+)
  writes a `.git` gitlink file with a path like
  `gitdir: ../main/.git/worktrees/wt1`. The previous code passed the
  raw pointer into `ancestors().nth(2)`, which yielded relative paths
  and emitted bogus `--mount source=../main,target=../main` args that
  Docker rejects or misinterprets. Now the pointer is resolved
  against the worktree root when it's not already absolute, matching
  how the sidebar daemon's worktree resolver already handles this.
  A test covers the relative case and asserts that all mount spec
  sources and targets are absolute.

- Directory entries: `is_file()` returns false for directories, so
  excluding a directory (e.g. `.aws`) previously produced a
  misleading "does not exist on disk" warning even though the path
  was there. Now we detect `is_dir()` in the candidate loop and emit
  a dedicated message explaining that only regular files can be
  masked.
@raine raine force-pushed the configurable-env-exclusion branch from 44c47d7 to bdf34ac Compare April 18, 2026 11:30
@raine raine merged commit 8181aae into raine:main Apr 18, 2026
@raine
Copy link
Copy Markdown
Owner

raine commented Apr 18, 2026

Thank you

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.

2 participants