Skip to content

sandbox exec always runs commands through a login shell, so sandbox-user startup files run before the requested command #2668

Description

@Dongni-Yang

Agent Diagnostic

Investigated from a source read of main at 5e2f0d1, not from a live cluster (this host cannot run a gateway — glibc 2.31, below the 2.32 minimum).

  • Searched existing issues for exec login shell profile: no matching issue found.
  • Latest release checked: v0.0.101. The code path below is unchanged on main at 5e2f0d1.
  • Traced the full sandbox exec path rather than reproducing, because the behavior is unconditional in source:
    1. handle_exec_sandbox builds a command string from the request, flattening argv and prepending --env pairs as shell assignments — crates/openshell-server/src/grpc/sandbox.rs:1776-1798.
    2. It relays that string over the supervisor SSH transport — stream_exec_over_relay, crates/openshell-server/src/grpc/sandbox.rs:1800-1806.
    3. The supervisor spawns it under a login shell — spawn_pipe_exec, crates/openshell-supervisor-process/src/ssh.rs:967 and 992-996.
  • Confirmed the PTY path does the same at crates/openshell-supervisor-process/src/ssh.rs:833-835.
  • Downstream evidence: [Ubuntu 24.04][Security] DCode connect probe executes sandbox-user login profile before failing closed NemoClaw#8624, where a QA-planted /sandbox/.bash_profile ran and left a marker file during a managed probe that passes exact argv and sets BASH_ENV= / ENV= to empty.

Description

sandbox exec runs every command through /bin/bash -lc, so the sandbox user's .profile / .bash_profile / .bashrc are sourced before the requested command starts.

// crates/openshell-supervisor-process/src/ssh.rs:992-996
|command| {
    let mut c = Command::new("/bin/bash");
    // Use login shell (-l) so that .profile/.bashrc are sourced and
    // tool-specific env vars (VIRTUAL_ENV, UV_PYTHON_INSTALL_DIR, etc.)
    // are available without hardcoding them here.
    c.arg("-lc").arg(command);
    c
}

The intent is clear and reasonable for interactive and tool-discovery use. The problem is that there is no way to opt out, which makes sandbox exec unsuitable for a managed or trusted probe:

  1. Argv is not preserved. build_remote_exec_command joins argv into a single shell string, so a caller passing exact argv after -- still gets shell evaluation.
  2. A trusted launcher cannot defend itself. A caller that execs a root-owned, mode-0755 helper still has the user's startup files run before that helper's first instruction.
  3. --env cannot suppress it. --env BASH_ENV= and --env ENV= become command-prefix assignments inside the string passed to bash -lc, so they apply only after the login shell has already sourced the profile.

Consequences for a caller that needs a trusted result:

  • Output integrity. A startup file writing to stdout/stderr interleaves with the command's output, so a caller parsing that output cannot distinguish command evidence from user-controlled text.
  • Side effects. A startup file executes on every exec, so it can create files or mutate state even when the requested command is read-only and the caller's boundary intends to bypass user startup files.

Reproduction Steps

  1. Create a sandbox and, as the sandbox user, write /sandbox/.bash_profile:

    printf 'echo PROFILE-RAN >&2\ntouch /sandbox/profile-ran\n' > /sandbox/.bash_profile
  2. From the host, run a command with exact argv and the shell startup variables cleared:

    openshell sandbox exec -n <name> --no-tty --env BASH_ENV= --env ENV= -- /bin/sh -c 'printf OK'
  3. Observe PROFILE-RAN on stderr, and /sandbox/profile-ran present, even though the requested command is /bin/sh -c 'printf OK' and both startup variables were cleared.

Environment

  • OS: Ubuntu 24.04 (downstream report), source read on Ubuntu 20.04
  • OpenShell: v0.0.85 in the downstream report; behavior verified unchanged in source on main at 5e2f0d1, and v0.0.101 is the latest release
  • Latest release checked: yes
  • Existing issues searched: yes, no duplicate found

Related issues

Suggested direction

Given #556, the least invasive option that still fixes this:

  1. An explicit opt-out on ExecSandboxRequest, for example login_shell: bool defaulting to today's behavior, so every existing caller is unaffected and only a caller that asks for a clean environment gets one.
  2. Alternatively, keep the login shell but apply request environment entries to the shell process itself rather than as command-prefix assignments, so BASH_ENV= and ENV= are honored before startup files are sourced. This is smaller, but it does not address .profile sourcing from -l, so it only partly fixes the problem.

I am happy to send a PR for whichever direction you prefer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions