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| {letmut 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.

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