fix(sandbox): detect an available login shell instead of hardcoding /bin/bash - #3147
fix(sandbox): detect an available login shell instead of hardcoding /bin/bash#3147akram wants to merge 3 commits into
Conversation
…bin/bash The built-in default sandbox command and the interactive SSH session hardcoded /bin/bash. Minimal images such as Alpine ship only /bin/sh (BusyBox ash), so sandbox startup failed with an opaque "No such file or directory (os error 2)" that never named the missing binary. Add openshell-core::shell with shell-path constants and a runtime detect_login_shell() that resolves a shell present in the sandbox image ($SHELL if executable, then bash, then /bin/sh). Use it for: - the built-in default command (only the default is remapped; explicit user commands are never rewritten), resolved in the supervisor so it inspects the sandbox filesystem rather than the gateway's - the SSH interactive shell - the SHELL environment variable Also name the program in the spawn error so a missing shell/binary is diagnosable instead of a bare ENOENT. Refs NVIDIA#3146 Signed-off-by: Akram <akram.benaissi@gmail.com>
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
This is a valid, focused fix for #3146, but three reachable regressions remain. The first cannot be anchored inline because the hardcoded PTY calls are unchanged lines outside the current diff.
Action required: address all three blocking findings and add relevant Fern documentation for the user-visible sandbox/SSH behavior, or obtain a maintainer-authored explanation that docs are intentionally unnecessary.
Warning — GATOR-158c9cb0-01 · Interactive PTY SSH still requires bash
Summary: A user running a supported sh-only image can use the new non-PTY SSH path, but a normal interactive SSH session requests a PTY and still executes /bin/bash at crates/openshell-supervisor-process/src/ssh.rs:1281 and :1286. Those calls are outside this diff, so this finding is summarized here rather than anchored inline. The interactive session still fails on Alpine after the separate Landlock grant is present.
Fix: Detect the shell in spawn_pty_shell and use it in both command branches while preserving -i and login-command arguments; add a deterministic sh-only PTY SSH regression test.
Verify: Start a sh-only sandbox with the required filesystem grant and run ssh -tt; the PTY session must start with /bin/sh, while the non-PTY path must continue to work.
Agent context
- Location:
crates/openshell-supervisor-process/src/ssh.rs:1441introduces detection only for the neighboring non-PTY adapter. - Ownership: The PR explicitly claims to fix interactive SSH but leaves its PTY adapter unchanged.
Blocking findings:
GATOR-158c9cb0-01: interactive PTY SSH retains the hardcoded bash dependency.GATOR-158c9cb0-02: explicit scratch-shaped commands are silently rewritten.GATOR-158c9cb0-03: Unix-only shell tests fail in the supported Windows workspace lane.
Carried findings:
- None
Gator metadata
- Validation: Focused bug fix tied to #3146 with a clear supported-user path and reproduction.
- Docs: Missing for a direct user-visible sandbox and SSH behavior change; a maintainer-authored explanation may establish that docs are intentionally unnecessary.
- Checks: Current-head DCO, vouch, dependency review, and workflow security checks pass; required Branch Checks and Helm Lint have not been dispatched.
- E2E: Required because sandbox lifecycle and supervisor behavior change;
test:e2ewill be dispatched after review blockers are resolved. - Head SHA:
158c9cb0656d306a805e85701e377baa0191c854 - Base SHA:
8e73f1db99e1a80d4f47303ce44e73a1204bf470 - Merge base SHA:
6c3980d01a7798cbfb9226c32a189a0726cc810c - Patch ID:
01d5be0815eb1a998bf00af7890f05583463fb6f - Gator payload:
8 - Review mode:
initial - Previous reviewed SHA: none
- Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:in-review
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
detect_login_shell() trusts any executable $SHELL, but the SSH path later invokes the result with -lc. An image with SHELL=/bin/false (or another executable that is not a compatible shell) passes detection and makes default/SSH command execution fail even though /bin/sh exists. Should $SHELL be restricted or probed for the shell semantics this code requires before preferring it?
That's correct. Good catch. Then, I will drop the $SHELL preference entirely and resolve only from the known candidate list ( (If there's a use case for honoring an operator-provided $SHELL, I'd gate it behind a basename allowlist of known shells instead — happy to go that way if preferred.) |
$SHELL is image/user-controlled and the detected shell is later invoked with `-lc`, so an executable that is not a compatible shell (e.g. SHELL=/bin/false) would pass the executable check and then break command execution even when /bin/sh is available. Resolve only from known shell paths instead. Also add a USR_BASH constant for /usr/bin/bash rather than a string literal in SHELL_CANDIDATES. Refs NVIDIA#3146 Signed-off-by: Akram <akram.benaissi@gmail.com>
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
Re-check After Author Update
Thanks @akram. I reviewed current head c37f287c632a1864b7dcdf8ac149f662be018088 after your September 3 comment and commit about removing the arbitrary $SHELL preference.
What I checked: the author-only delta in openshell-core::shell, plus all three durable findings from the prior Gator review. Shell selection now uses only the known candidate list, so the concern you acknowledged is resolved.
Disposition: partially resolved. The latest commit does not change the three existing Gator obligations.
Remaining items:
GATOR-158c9cb0-01: make PTY-based interactive SSH use the detected shell and add the sh-only PTY regression test.GATOR-158c9cb0-02: preserve omitted-default provenance so an explicit scratch-shaped command is never rewritten.GATOR-158c9cb0-03: make the shell tests portable to the supported Windows lane.- Add relevant Fern documentation for the user-visible sandbox/SSH behavior, or obtain a maintainer-authored explanation that docs are intentionally unnecessary.
Action required: @akram, please address the carried findings and docs gate in a new commit.
Gator metadata
- Validation: Focused bug fix tied to #3146 with a clear supported-user path and reproduction.
- Docs: Missing for a direct user-visible sandbox and SSH behavior change; a maintainer-authored explanation may establish that docs are intentionally unnecessary.
- Checks: Current-head gate publishers are queued or running; Branch Checks, Helm Lint, and required E2E have not been dispatched.
- E2E: Required because sandbox lifecycle and supervisor behavior change; dispatch waits until review blockers are resolved.
- Head SHA:
c37f287c632a1864b7dcdf8ac149f662be018088 - Base SHA:
8e73f1db99e1a80d4f47303ce44e73a1204bf470 - Merge base SHA:
6c3980d01a7798cbfb9226c32a189a0726cc810c - Patch ID:
b5cf5c8505564a34fd3d35b994a3617f4e7ae2e4 - Gator payload:
8 - Review mode:
follow_up - Previous reviewed SHA:
158c9cb0656d306a805e85701e377baa0191c854 - Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:in-review
|
Label |
|
/ok to test 1353fab |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
Re-check After Author Update
Thanks @akram. I reviewed current head 1353fab2e1a82e0d08132e430b86664ce23e8500 after your update addressing the prior Gator review.
What I checked: the author-only delta from c37f287c632a1864b7dcdf8ac149f662be018088, all three durable findings, and the published sandbox documentation. The PTY path now uses the detected shell, explicit-command provenance is preserved, Unix-only tests are gated from Windows, and the Fern documentation describes the fallback behavior.
Disposition: resolved. No blocking findings remain. The required current-head Branch Checks, Helm Lint, and E2E workflows are queued or running.
Remaining items:
- No blocking review items remain.
Gator metadata
- Validation: Focused bug fix tied to #3146 with a clear supported-user path and reproduction.
- Docs: Updated in
docs/sandboxes/manage-sandboxes.mdx; navigation changes are not needed for an existing page. - Checks: Current-head Branch Checks and Helm Lint are queued or running.
- E2E:
test:e2eapplied;/ok to test 1353fab2e1a82e0d08132e430b86664ce23e8500created the mirror and Branch E2E Checks is running. - Head SHA:
1353fab2e1a82e0d08132e430b86664ce23e8500 - Base SHA:
8e73f1db99e1a80d4f47303ce44e73a1204bf470 - Merge base SHA:
6c3980d01a7798cbfb9226c32a189a0726cc810c - Patch ID:
6ae14a24b564d6aa76c8c9b5226a3c6ce5b1d8fc - Gator payload:
8 - Review mode:
follow_up - Previous reviewed SHA:
c37f287c632a1864b7dcdf8ac149f662be018088 - Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:watch-pipeline
|
/hold |
|
@johntmyers @sylvesterkaczmarek one more The fix I have adds a |
|
For the gateway-path piece (making the default-shell remap trigger when the gateway substitutes an omitted command), I put the change on a separate branch instead of pushing it here, since it touches the public https://github.com/akram/OpenShell/tree/fix/sandbox-shell-detection-provenance (commit It adds a If you'd rather not touch the public spec, the alternative keeps it internal: the gateway stops persisting a substituted Which do you prefer? |
|
@akram I'd prefer option 2, keep this as an internal spec update and keep the public command empty. one thing I'd suggest is that we log from the supervisor what command was resolved when the supervisor has to select |
|
@akram question - do we need to add a new flag even on the internal spec? would an empty command from the public API semantically mean the same thing? |
…y command = default) Addresses review: interactive PTY SSH now uses the detected shell, the shell tests are portable across the Windows lane, and default-shell provenance is carried without a new spec field. An omitted command is left empty end to end and resolved in the supervisor, which is the only place that sees the sandbox image: - The CLI forwards the command as-is; the gateway persists an omitted command as empty (no baked /bin/bash -l) and requests a TTY. - MainProcessConfig carries the command empty (the transport now allows it); the supervisor resolves a login shell that exists in the sandbox image (bash when present, otherwise /bin/sh on minimal images like Alpine) and logs the resolved shell. - Interactive PTY SSH (spawn_pty_shell) uses the detected shell; a shared build_ssh_shell_command helper covers the PTY and non-PTY paths, with a deterministic sh-only regression test. - Unix-only shell tests are gated with cfg(unix). An explicit command is always run verbatim. Refs NVIDIA#3146 Signed-off-by: Akram <akram.benaissi@gmail.com>
1353fab to
89f8783
Compare
|
Agreed @johntmyers, that makes things easier. I wasn't comfortable with a public API change either, so I've rewritten that part the way you suggested: no flag, an omitted command stays empty end to end, and the supervisor resolves the default login shell against the sandbox image (bash when present, otherwise |
|
/ok to test 89f8783 |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
Re-check After Author Update
Thanks @akram. I checked current head 89f8783e4fd01b1e154300b4e586791c4d3b271e after your September 3 rewrite following @johntmyers's request to keep omitted-command semantics internal. The author-only delta now carries an omitted command empty through the gateway and driver transport, resolves it inside the sandbox supervisor, logs the selected shell, and continues to preserve explicit commands verbatim.
Disposition: resolved. The required critical-only follow-up found no newly introduced Critical defect, all prior Gator findings remain resolved, and current-head Branch Checks, Helm Lint, and E2E workflows are queued or running.
Blocking findings:
- No blocking findings remain.
Carried findings:
- None
Gator metadata
- Validation: Focused bug fix tied to accepted issue #3146 with a clear supported-user path and reproduction.
- Docs: Updated in
docs/sandboxes/manage-sandboxes.mdx; navigation changes are not needed for an existing page. - Checks: Current-head Branch Checks and Helm Lint are queued or running; DCO and the required-gate publisher pass.
- E2E:
test:e2eis applied;/ok to test 89f8783e4fd01b1e154300b4e586791c4d3b271ecreated the current-head mirror and Branch E2E Checks is queued. - Head SHA:
89f8783e4fd01b1e154300b4e586791c4d3b271e - Base SHA:
17171cd9337a2181d4bb5a9e711e1f2ad5f69388 - Merge base SHA:
6c3980d01a7798cbfb9226c32a189a0726cc810c - Patch ID:
5ba4328616c21440a2fcbc75d33934a1c3ef781f - Gator payload:
8 - Review mode:
critical_only - Previous reviewed SHA:
1353fab2e1a82e0d08132e430b86664ce23e8500 - Review budget exhausted: yes
- Maintainer decision required: no
- Next state:
gator:watch-pipeline
Summary
The built-in default sandbox command and the interactive SSH session hardcoded
/bin/bash. Minimal images such as Alpine ship only/bin/sh(BusyBoxash), so sandbox startup failed with an opaquefailed to spawn sandbox entrypoint process: No such file or directory (os error 2)that never named the missing binary. This resolves a login shell that actually exists in the sandbox image, and names the program in the spawn error.Related Issue
Refs #3146. This addresses the
/bin/bashhardcoding root cause. Note: a fully stock Alpine image additionally requires the Landlock filesystem baseline (PROXY_BASELINE_READ_ONLY) to grant/binand/sbinon non-usr-merged images — a separate root cause I will file and fix on its own.Changes
openshell-core::shell: shell-path constants (BASH,POSIX_SH,SHELL_CANDIDATES) and a runtimedetect_login_shell()that resolves a shell present in the current root filesystem ($SHELLif executable, then bash, then/bin/sh), plusis_executable().openshell-sandbox(main.rs): when the command is the built-in default, remap its shell to a detected one. Only the default is remapped — explicit user commands are never rewritten. Resolution runs in the supervisor so it inspects the sandbox filesystem, not the gateway's.openshell-supervisor-process(ssh.rs,process.rs): use the detected shell for the interactive SSH session and theSHELLenv var.Testing
Verified on OpenShift (ROSA HCP, Kubernetes compute driver):
Before: an
alpine:3.21sandbox crash-looped with a bare ENOENT.After: the supervisor logs
default shell not found in sandbox image; falling back to a detected shell, resolves/bin/sh, and the spawn error (when a binary is genuinely missing) now names it, e.g.failed to spawn sandbox entrypoint process '/bin/sh'. The Alpine sandbox reachesRunningonce/bin//sbinare also granted (tracked separately).Bash-based images are unaffected (bash is still preferred when present).
mise run pre-commitpasses —cargo fmt --check,cargo clippy -D warnings, license headers, and unit tests pass. Thehelm:lintstep fails locally due to a missingpostgresqlchart dependency unrelated to this change.Unit tests added/updated —
openshell-core::shell(4 tests).E2E tests added/updated (if applicable) — verified manually end-to-end on OpenShift; no automated e2e added.
Checklist