Skip to content

Relative process.cwd is resolved against the host process's working directory #873

Description

Relevant area(s)

Linux, macOS, Windows

Brief description of your issue

A relative process.cwd is never validated, so it is resolved against the host process's current directory. The sandboxed child then runs in a directory chosen by host state that the policy never granted — silently, with a successful exit.

Nothing in the config pipeline checks the shape of the value:

  • config_parser.rs:802 takes process.cwd verbatim (process.cwd.unwrap_or_default()).
  • validator.rs has no rule for working_directory at all (it only appears in a test fixture).
  • Backends hand it straight to the OS: Seatbelt via Command::current_dir (seatbelt_runner.rs:226), Bubblewrap via --chdir (bwrap_command.rs:260), Windows ProcessContainer via CreateProcessW (working_directory.rs:71).

For an absolute path this is correct and matches the documented contract. For a relative path, current_dir / --chdir resolve it against the launching process's cwd, so the effective sandbox cwd depends on where the host binary happened to be invoked from.

This defeats the reason the resolution logic exists in the first place (core/wxc_common/src/models.rs:771):

Backends that must not let the child inherit the host process's cwd use this to fall back to a policy-granted path.

An explicit relative cwd re-introduces exactly that inheritance through the back door.

Steps to reproduce

On macOS (same shape applies to Bubblewrap on Linux and ProcessContainer on Windows):

  1. mkdir -p /tmp/mxccwd/sub
  2. Build a policy granting only /tmp/mxccwd as readwritePaths, then:
    let mut request = build_request(&policy, None)?;
    request.set_script("pwd").set_working_directory("sub");
    let output = run(request)?;
  3. Run the resulting binary from /tmp/mxccwd.

Equivalently via a JSON config with "process": { "cwd": "sub" }.

Expected behavior

A relative process.cwd is rejected with a malformed-request / policy_validation error naming the value. The sandboxed child's working directory should never depend on the host process's cwd.

Actual behavior

Observed output from the reproduction above:

host_cwd="/private/tmp/mxccwd" requested="sub" -> "/private/tmp/mxccwd/sub" (Exited(0))

The child ran in a host-relative directory, exit code 0, no error and no warning. Invoking the same binary from a different directory silently changes the sandbox's working directory.


Note for whoever picks this up: the check is not a shared one-liner. "Absolute" is backend-relative — C:\x is not absolute per Path::is_absolute on Unix, and /x is a valid in-container path for WSLc but not a valid Windows host path. The rule likely belongs at each backend's resolution point (or as a per-backend predicate passed into the shared helper), not as a single check in validator.rs.

Related: #872 (WSLc one-shot silently drops an untranslatable process.cwd) — same field, same class of "wrong working directory, no diagnostic" failure, but a separate root cause and fix.

Metadata

Metadata

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions