You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Identified during review of PRs #2369 and #2370 (fix for #2337). Three independent review agents flagged the same vulnerability in the SSH server accept loop.
Finding:crates/openshell-supervisor-process/src/ssh.rs:141 uses ? on listener.accept().await, so any transient error (EMFILE, ENFILE, ECONNABORTED) propagates out of run_ssh_server() and permanently kills the SSH accept loop. The spawned task in crates/openshell-supervisor-process/src/run.rs:238 is fire-and-forget — the JoinHandle is dropped, so the sandbox has no way to detect that the SSH server has exited.
This is the same class of bug as #2337, but in the exec relay path instead of the proxy path.
Description
What happens: When the SSH server's TCP listener hits a transient error like EMFILE (too many open file descriptors), the accept loop exits permanently. The sandbox continues to report Ready even though all subsequent exec relays will fail. The OCSF event is emitted at Critical severity, but no recovery or sandbox termination occurs.
Why this matters: The proxy and SSH server share the supervisor's FD table. FD exhaustion triggered by proxy connections (the scenario in #2337) can simultaneously kill the SSH server. PR #2369 keeps the proxy alive through backoff, but during the backoff period the SSH accept can still fail and exit permanently. The proxy recovers, but exec relays are dead.
This means the invariant from #2337 — "Ready must imply the sandbox's required policy proxy and exec relay are still operational" — is only enforced for the proxy path, not the SSH/exec relay path.
Reproduction Steps
Start a sandbox with both proxy and SSH server enabled
Apply the same two-layer defense pattern from PRs #2369 and #2370:
Retry with backoff (ssh.rs): Replace listener.accept().await.into_diagnostic()? with a sleep-and-continue pattern matching proxy.rs. Reuse or mirror is_fd_exhaustion_error() and accept_backoff().
Agent Diagnostic
Identified during review of PRs #2369 and #2370 (fix for #2337). Three independent review agents flagged the same vulnerability in the SSH server accept loop.
Finding:
crates/openshell-supervisor-process/src/ssh.rs:141uses?onlistener.accept().await, so any transient error (EMFILE, ENFILE, ECONNABORTED) propagates out ofrun_ssh_server()and permanently kills the SSH accept loop. The spawned task incrates/openshell-supervisor-process/src/run.rs:238is fire-and-forget — theJoinHandleis dropped, so the sandbox has no way to detect that the SSH server has exited.This is the same class of bug as #2337, but in the exec relay path instead of the proxy path.
Description
What happens: When the SSH server's TCP listener hits a transient error like EMFILE (too many open file descriptors), the accept loop exits permanently. The sandbox continues to report
Readyeven though all subsequent exec relays will fail. The OCSF event is emitted atCriticalseverity, but no recovery or sandbox termination occurs.What should happen: The SSH server should either:
Why this matters: The proxy and SSH server share the supervisor's FD table. FD exhaustion triggered by proxy connections (the scenario in #2337) can simultaneously kill the SSH server. PR #2369 keeps the proxy alive through backoff, but during the backoff period the SSH accept can still fail and exit permanently. The proxy recovers, but exec relays are dead.
This means the invariant from #2337 — "Ready must imply the sandbox's required policy proxy and exec relay are still operational" — is only enforced for the proxy path, not the SSH/exec relay path.
Reproduction Steps
listener.accept().awaitreturns EMFILE?operator propagates the error, exitingrun_ssh_server()openshell execcommands fail because the SSH server is goneEnvironment
crates/openshell-supervisor-process/src/ssh.rs:141,crates/openshell-supervisor-process/src/run.rs:238-262Suggested Fix
Apply the same two-layer defense pattern from PRs #2369 and #2370:
Retry with backoff (
ssh.rs): Replacelistener.accept().await.into_diagnostic()?with a sleep-and-continue pattern matchingproxy.rs. Reuse or mirroris_fd_exhaustion_error()andaccept_backoff().Exit notification (
ssh.rs+lib.rs): Add a oneshot drop-guard channel (matching the_proxy_exit_guardpattern from PR fix(sandbox): terminate sandbox when proxy accept loop exits unexpectedly #2370). Store theJoinHandleor exit receiver and race it in the sandbox'stokio::select!wait paths.Related