Skip to content

feat(minimald): support arbitrary session exec - #1173

Merged
twitchyliquid64 merged 1 commit into
mainfrom
tom/sftp
Aug 5, 2026
Merged

feat(minimald): support arbitrary session exec#1173
twitchyliquid64 merged 1 commit into
mainfrom
tom/sftp

Conversation

@twitchyliquid64

@twitchyliquid64 twitchyliquid64 commented Aug 5, 2026

Copy link
Copy Markdown
Member

Makes it possible to launch arbitrary commands within the session.

min attach <name or id> -c 'pwd'

Summary by CodeRabbit

  • New Features

    • Run SSH commands directly inside existing session sandboxes.
    • Automatically start or reuse session hosts for command execution.
    • Preserve working directories and environment variables across commands.
    • Propagate variables from installed packages to active shells.
    • Improve filesystem and namespace isolation during command execution.
  • Bug Fixes

    • Corrected routing for valid session commands.
    • Ensured later environment values consistently override earlier ones.

Note

Add arbitrary SSH exec support for commands run inside session namespaces

  • Adds a new nsenter-based injection pathway in crates/minimald/src/nsenter.rs that re-execs the minimald binary as a shim to setns into the session's namespaces, then spawns the target program with the correct cwd and environment.
  • SSH exec requests that are not min subcommands are now routed into the session sandbox via SessionExec in crates/minimald/src/exec.rs instead of being rejected.
  • Adds SessionGuard trait and SessionEnvironment struct so Host can read current working directory and environment variables when building in-session commands.
  • Adds SessionHandle::ensure_host so a sandbox host can be started without attaching a terminal, enabling headless exec.
  • Exposes Sandbox::command_cwd and Sandbox::command_env from sandbox2 config so the injection path uses the same environment logic as direct container invocations.
  • Risk: namespace joining uses pidfd_open and a single setns call; if the session leader exits between pid resolution and injection, the shim will fail with a NsenterError.

Macroscope summarized f3ee61d.

@twitchyliquid64
twitchyliquid64 requested a review from a team as a code owner August 5, 2026 01:43
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds session-scoped SSH command execution, shared runtime environment propagation, and Linux namespace injection. It centralizes sandbox command environments, refactors session host launching, adds integration tests, and enables the nix sched feature.

Changes

Session execution

Layer / File(s) Summary
Command environment construction
crates/sandbox2/src/config.rs, crates/sandbox2/src/lib.rs
Sandbox configuration now provides command working directories and layout-specific environments.
Runtime environment propagation
crates/minimald/src/env.rs
Acquired variables update daemon state and the session shell, with last-write-wins behavior.
Session host command execution
crates/minimald/src/session.rs, crates/minimald/src/session_host.rs
Session hosts can launch without attached clients and construct commands with session state.
Namespace injection shim
crates/minimald/src/nsenter.rs, crates/minimald/src/main.rs, crates/minimald/src/lib.rs, crates/minimald/tests/nsenter_integration.rs
The Linux shim joins session namespaces, applies command state, runs a process, and returns its status.
SSH session command routing
crates/minimald/src/exec.rs
Non-min SSH commands now run inside the session with tracing and daemon filesystem isolation tests.

Build dependency

Layer / File(s) Summary
Enable nix scheduling support
Cargo.toml
The workspace enables the sched feature for nix.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SSHClient
  participant SessionExec
  participant SessionHandle
  participant HostHandle
  participant NsenterShim
  SSHClient->>SessionExec: submit non-min command
  SessionExec->>SessionHandle: ensure_host
  SessionExec->>HostHandle: command_in_session
  HostHandle->>NsenterShim: run with session pidfd and namespaces
  NsenterShim-->>SSHClient: return process output and exit status
Loading

Possibly related PRs

Suggested reviewers: norrietaylor

Poem

A rabbit sends commands through the night,
Into session namespaces out of sight.
Hosts wake, environments flow,
Pidfds guide the processes below.
sched joins the build just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the main change but omits the required Testing and Checklist sections from the repository template. Add a Testing section with commands and results, and add the required Checklist items with applicable boxes checked.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: support for arbitrary session execution.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tom/sftp

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
crates/minimald/src/exec.rs (1)

495-498: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Share the session shell path instead of redefining it.

SESSION_SHELL is /usr/bin/bash. sandbox2::config::Config::command_env sets SHELL to the same literal for the session layout. Two crates now hold the same hardcoded path. If the session shell moves, the two values diverge silently, and the injected command runs a shell that does not match $SHELL.

Export the path from sandbox2 and use it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/exec.rs` around lines 495 - 498, Remove the local
SESSION_SHELL definition in the session exec logic and expose the shared
shell-path constant from sandbox2::config for external use. Update the session
launcher and injected-command setup to reference that exported constant,
ensuring command execution and SHELL configuration remain synchronized.
crates/minimald/src/session.rs (1)

1329-1334: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Propagate the record read failure instead of unwrapping it.

launch_host calls self.record.record().await.unwrap(). A store read error panics the session actor task. Every caller then sees only "session actor terminated", and the session becomes unusable until the daemon restarts.

ensure_host already reads the record fallibly at lines 1382-1386 and maps the error to AttachError::LoadoutFailed. Do the same here so a transient store failure returns an error to the caller.

♻️ Proposed change
-        let record = self.record.record().await.unwrap();
+        let record = self
+            .record
+            .record()
+            .await
+            .map_err(AttachError::LoadoutFailed)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/session.rs` around lines 1329 - 1334, Update launch_host
to handle self.record.record().await errors without unwrapping, mapping the
failure to AttachError::LoadoutFailed consistently with ensure_host. Preserve
the existing successful record flow and return the mapped error to the caller so
the session actor remains running.
crates/sandbox2/src/config.rs (1)

279-286: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the BoundDir HOME source with the synthesized passwd entry.

Config::build (line 613) derives the synthesized /etc/passwd home from std::env::home_dir(). command_env derives it from std::env::var("HOME"). If the two disagree, the sandbox user's passwd home does not match $HOME inside the sandbox. Use one source for both.

♻️ Proposed change
-            WdSetup::BoundDir { .. } => match std::env::var("HOME") {
-                Ok(h) => set("HOME", &h),
-                Err(_) => set("HOME", "/state/home"),
-            },
+            WdSetup::BoundDir { .. } => match std::env::home_dir()
+                .and_then(|p| p.to_str().map(String::from))
+            {
+                Some(h) => set("HOME", &h),
+                None => set("HOME", "/state/home"),
+            },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sandbox2/src/config.rs` around lines 279 - 286, Update the BoundDir
branch in command_env to derive HOME from the same source as Config::build’s
synthesized passwd entry, namely std::env::home_dir(), and preserve the existing
/state/home fallback when no home directory is available. Remove the separate
std::env::var("HOME") lookup so both values remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald/src/exec.rs`:
- Around line 876-893: Redact or avoid recording raw command arguments in the
`tracing::info!` request log and the `exec_span` created in the non-`min`
dispatch path. Reuse the project’s existing command-redaction mechanism if
available; otherwise log only a safe command identifier while preserving session
and dispatch context.
- Around line 516-522: Update the session command lifecycle around Tok ioProcess
and shim_main so dropping the client or shim terminates the in-session process
tree, not just the __nsenter shim. Propagate termination from shim_main to the
spawned child and its descendants, while preserving the existing piped stdio and
kill_on_drop behavior.

In `@crates/minimald/src/lib.rs`:
- Line 13: Gate the `nsenter` module declaration in `lib.rs` with
`#[cfg(target_os = "linux")]`, and apply the same condition to every
`minimald::nsenter` reference in `main.rs`. Ensure non-Linux builds neither
compile nor access the Linux-specific module.

In `@crates/minimald/src/main.rs`:
- Around line 476-485: Update the late-parsed Nsenter arm in async_main’s
command match to return a descriptive error instead of calling unreachable!, and
make the diagnostic state that Nsenter must be intercepted before entering the
daemon runtime. Remove the inaccurate claim that CLI arguments are not parsed
during interception; leave the Completions and other command handling unchanged.

In `@crates/minimald/src/nsenter.rs`:
- Around line 480-485: Validate args.pidfd in shim_main before calling
OwnedFd::from_raw_fd, rejecting values below PIDFD_FD with the proposed
NsenterError::BadPidfd variant. Update the safety comment to state the validated
descriptor invariant and remove the inaccurate claim that arbitrary wrong
descriptors are harmless; preserve adoption only for accepted pidfd values.

---

Nitpick comments:
In `@crates/minimald/src/exec.rs`:
- Around line 495-498: Remove the local SESSION_SHELL definition in the session
exec logic and expose the shared shell-path constant from sandbox2::config for
external use. Update the session launcher and injected-command setup to
reference that exported constant, ensuring command execution and SHELL
configuration remain synchronized.

In `@crates/minimald/src/session.rs`:
- Around line 1329-1334: Update launch_host to handle self.record.record().await
errors without unwrapping, mapping the failure to AttachError::LoadoutFailed
consistently with ensure_host. Preserve the existing successful record flow and
return the mapped error to the caller so the session actor remains running.

In `@crates/sandbox2/src/config.rs`:
- Around line 279-286: Update the BoundDir branch in command_env to derive HOME
from the same source as Config::build’s synthesized passwd entry, namely
std::env::home_dir(), and preserve the existing /state/home fallback when no
home directory is available. Remove the separate std::env::var("HOME") lookup so
both values remain consistent.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1efdc6a-adc2-43cf-976b-0999265902cc

📥 Commits

Reviewing files that changed from the base of the PR and between 3b26ad6 and f289622.

📒 Files selected for processing (11)
  • Cargo.toml
  • crates/minimald/src/env.rs
  • crates/minimald/src/exec.rs
  • crates/minimald/src/lib.rs
  • crates/minimald/src/main.rs
  • crates/minimald/src/nsenter.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/tests/nsenter_integration.rs
  • crates/sandbox2/src/config.rs
  • crates/sandbox2/src/lib.rs

Comment thread crates/minimald/src/exec.rs
Comment thread crates/minimald/src/exec.rs
Comment thread crates/minimald/src/lib.rs
Comment thread crates/minimald/src/main.rs
Comment thread crates/minimald/src/nsenter.rs
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald/tests/nsenter_integration.rs`:
- Around line 17-22: Update the nsenter integration test documentation near the
module-level command to use the repository’s applicable just recipe instead of
invoking cargo directly, while forwarding MINIMALD_NSENTER_TEST=1 and the
ignored-test arguments. If no suitable recipe exists, add one and reference it
in the command.
- Around line 51-72: Update the sandbox helper around sandbox to return a local
RAII guard owning hakoniwa::Child instead of the raw child; implement Drop for
the guard to call kill and then wait, ensuring the /bin/sleep process is cleaned
up even when tests panic. Preserve the existing process spawning and command
configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed5d2a89-a9f0-45f3-88fe-0425073d365c

📥 Commits

Reviewing files that changed from the base of the PR and between 999d64c and f3ee61d.

📒 Files selected for processing (11)
  • Cargo.toml
  • crates/minimald/src/env.rs
  • crates/minimald/src/exec.rs
  • crates/minimald/src/lib.rs
  • crates/minimald/src/main.rs
  • crates/minimald/src/nsenter.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/tests/nsenter_integration.rs
  • crates/sandbox2/src/config.rs
  • crates/sandbox2/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • Cargo.toml
  • crates/sandbox2/src/lib.rs
  • crates/minimald/src/main.rs
  • crates/minimald/src/lib.rs
  • crates/sandbox2/src/config.rs
  • crates/minimald/src/exec.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/src/nsenter.rs
  • crates/minimald/src/env.rs

Comment on lines +17 to +22
//! `#[ignore]`, and additionally early-returns unless `MINIMALD_NSENTER_TEST` is
//! set, so neither a plain `cargo test` run nor the ignored-test sweep attempts
//! namespace work on a host that may not allow it. Needs unprivileged user
//! namespaces (no root, unlike the netns proofs), a kernel with
//! `CONFIG_PROC_CHILDREN`, and `setns` pidfd support (Linux 5.8+):
//! `MINIMALD_NSENTER_TEST=1 cargo test -p minimald --test nsenter_integration -- --include-ignored`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a just recipe in the test command.

Line 22 directs contributors to run cargo test directly. Replace it with the applicable just recipe that forwards MINIMALD_NSENTER_TEST. Add a recipe if none exists.

As per coding guidelines, build and test only through just; use repository recipes rather than hand-written cargo commands, and add a recipe when none exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/tests/nsenter_integration.rs` around lines 17 - 22, Update
the nsenter integration test documentation near the module-level command to use
the repository’s applicable just recipe instead of invoking cargo directly,
while forwarding MINIMALD_NSENTER_TEST=1 and the ignored-test arguments. If no
suitable recipe exists, add one and reference it in the command.

Source: Coding guidelines

Comment thread crates/minimald/tests/nsenter_integration.rs
@twitchyliquid64
twitchyliquid64 enabled auto-merge (rebase) August 5, 2026 02:11
@twitchyliquid64
twitchyliquid64 merged commit e0f67c8 into main Aug 5, 2026
30 checks passed
@twitchyliquid64
twitchyliquid64 deleted the tom/sftp branch August 5, 2026 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants