Isolation & security

jackin-exec: Secure Execution Wrapper

Status: Partially implemented — the full mechanism is wired and shipped end to end (config schema, dispatch, picker, host.sock resolution, MCP tool). Linux host.sock callers are now authenticated with peer credentials so direct agent-side socket calls cannot bypass the picker; non-Linux/Docker Desktop coverage remains with the future daemon track. One credential-materialization gap remains, and the flow has not had a live smoke pass through jackin load.

Shipped facts

jackin-exec is a subcommand of jackin-capsule, invoked either as jackin-exec <command> [args...] (argv0 dispatch) or jackin-capsule exec <command>. The on_demand flag on EnvValue (EnvValue::Extended plus OpRef.on_demand in crates/jackin-core/src/env_value.rs) marks env vars that are never resolved or injected at container launch — crates/jackin-env/src/resolve.rs filters them out before docker run, and the workspace migration chain carries the schema at v1alpha8 with fixtures through from-v1alpha7. Launch injects JACKIN_EXEC_BINDINGS (the on-demand var names, not values) as an always-available env var, which drives both an entrypoint system-prompt block and the jackin_exec MCP tool (crates/jackin-capsule/src/mcp_server.rs, registered from crates/jackin-capsule/src/runtime_setup.rs whenever bindings are non-empty).

Dialog::ExecPicker is wired end to end in the daemon: an ExecCommand control message opens the picker (built from the workspace's on-demand bindings), the reply is deferred while the operator toggles credentials (Space) and confirms (Enter) or cancels (Esc). On confirm, the daemon resolves the selected credentials through host.sock, runs the command with them injected on the CommandBuilder, redacts the output, and replies ExecResult; on cancel or any resolver/spawn error it replies ExecDenied and runs nothing — fail-closed at the daemon dispatch path. The picker state machine and selection/confirm dispatch are unit-tested (crates/jackin-capsule/src/exec/tests.rs, crates/jackin-capsule/src/daemon/tests.rs).

host.sock (started by the launch path, bind-mounted to /jackin/run/host.sock) enforces an allow-list — it resolves only the operator-configured on-demand bindings, never arbitrary refs. On Linux it also authenticates the caller with UnixStream::peer_cred and accepts only the capsule daemon container-init peer, so a direct in-container process cannot resolve configured credentials without going through the picker. Non-Linux hosts and Docker Desktop socket-bridging remain residual until the daemon-based host bridge replaces this ad-hoc listener. See jackin-exec design record for the full protocol, type changes, and phased implementation history.

Remaining work

  1. Live smoke of the picker/render/keystroke path through jackin load — no integration or e2e coverage exists yet, only unit tests of the state machine.
  2. Extend caller authentication beyond Linux same-kernel SO_PEERCRED, or retire the gap by moving resolution into the future jackin daemon / host bridge.
  3. Temp-file materialization for key-type credentials — write an SSH private key to /jackin/run/secrets/<uuid> and set SSH_KEY_FILE, deleting after exit. Today an SSH key resolves as a raw SSH_KEY=<pem> env var, which ssh cannot consume directly.

On this page