Skip to content

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

Closed
letv1nnn/OpenShell
#9

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

  1. letv1nnn commented on Aug 13, 2026

    @letv1nnn
    Contributor

    Hey @Dongni-Yang, I'd like to take this one.

  2. Dongni-Yang commented on Aug 13, 2026

    @Dongni-Yang
    Author

    Sure, go ahead. Note that I'm not a maintainer of OpenClaw, so I can't assign you to this issue or merge your PR.

  3. krishicks commented on Aug 13, 2026

    @krishicks
    Collaborator

    @Dongni-Yang I'm working on revising our issue/bug template and had my agent rewrite this issue in the new style. Can you review this and tell me if it accurately captures your issue?


    User Story

    As a platform operator or integration author running managed checks inside an OpenShell sandbox, I want to execute a command without loading sandbox-user shell startup files so that the command’s output and side effects reflect only the requested operation.

    Problem Statement

    openshell sandbox exec always starts the requested command through a login shell. This causes sandbox-controlled files such as .profile, .bash_profile, and indirectly .bashrc to run before the requested command.

    Callers cannot opt out of this behavior. Passing an explicit executable after --, disabling TTY allocation, or clearing BASH_ENV and ENV does not prevent login-profile execution because OpenShell starts the login shell before applying those command-level environment assignments.

    Impact / Why This Matters

    Login-shell behavior is useful for interactive development because it makes tool-specific environment configuration available automatically. It is insufficient for managed probes, health checks, validation commands, and integrations that need predictable output and side effects.

    A sandbox user can modify a startup file to:

    • Write arbitrary text to stdout or stderr before the requested command runs.
    • Create or modify files during an otherwise read-only check.
    • Change environment variables or command resolution.
    • Prevent the requested executable from starting.
    • Make a managed check report a result that does not come exclusively from the requested operation.

    Launching a root-owned or otherwise trusted helper does not address the problem because the startup file runs before that helper’s first instruction. Callers must currently avoid sandbox exec, modify the sandbox user’s profile, or add custom result-validation logic. These workarounds are fragile and prevent sandbox exec from serving as a predictable execution boundary for automation.

    Proposed Design

    Add an explicit execution mode that runs a command without loading shell startup files while preserving the current login-shell behavior by default.

    For example:

    openshell sandbox exec \
      --name <sandbox> \
      --no-login-shell \
      --no-tty \
      --env BASH_ENV= \
      --env ENV= \
      -- /usr/local/bin/managed-probe

    The user-facing behavior should be:

    1. Without --no-login-shell, sandbox exec retains its current behavior so existing interactive and tool-discovery workflows continue to receive profile-defined environment configuration.
    2. With --no-login-shell, OpenShell does not source user-controlled login or interactive shell startup files before running the requested command.
    3. The clean behavior applies consistently to TTY and non-TTY execution.
    4. Environment values supplied with --env are applied before command execution and do not depend on command-prefix shell assignments.
    5. Output written before the requested command starts is not introduced by user shell startup files.
    6. Startup files cannot create side effects or prevent the requested command from starting in this mode.
    7. SDK and gRPC callers can request the same mode without depending on CLI-specific behavior.

    The CLI help and sandbox documentation should explain the tradeoff:

    • Default login-shell mode is intended for user-oriented commands that rely on profile-defined tool configuration.
    • Non-login mode is intended for automation and managed checks that require predictable startup behavior.
    • Non-login mode does not necessarily promise direct argv execution or eliminate all shell interpretation. Replacing the existing shell-string transport remains separate from this narrowly scoped behavior.

    This design should also account for images that provide a POSIX shell but not Bash, as reported in #2290, so the new mode does not introduce or preserve an unnecessary /bin/bash requirement.

  4. added
    state:acceptedA maintainer decided OpenShell should pursue this issue
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Aug 13, 2026
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