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
- 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. - Extend caller authentication beyond Linux same-kernel
SO_PEERCRED, or retire the gap by moving resolution into the future jackin❯ daemon / host bridge. - Temp-file materialization for key-type credentials — write an SSH private key to
/jackin/run/secrets/<uuid>and setSSH_KEY_FILE, deleting after exit. Today an SSH key resolves as a rawSSH_KEY=<pem>env var, whichsshcannot consume directly.
Related work
- jackin-exec design record — full protocol, Rust type changes, picker/host.sock design, and implementation-phase history
- Agent isolation architecture — master architecture doc; jackin-exec is Layer 3
- Container credential exposure — the problem this item solves
- jackin❯ daemon — future: daemon replaces the Phase 1 ad-hoc host.sock listener
- Host bridge — secrets and approved host actions — future daemon-based credential resolution that retires host.sock
- Process-level sandboxing — complementary — zerobox handles per-operation filesystem/network policy
- Apple Container backend — jackin-exec works on any backend
- Session contract and explain mode — jackin-exec calls and picker selections should appear in session contract output