Skip to content

feat(docker): localhost sshd → agentbox open sshfs + Finder/Codex launchers#175

Merged
madarco merged 4 commits into
nightlyfrom
feat/docker-open-ssh
Jul 8, 2026
Merged

feat(docker): localhost sshd → agentbox open sshfs + Finder/Codex launchers#175
madarco merged 4 commits into
nightlyfrom
feat/docker-open-ssh

Conversation

@madarco

@madarco madarco commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Every docker box now runs an always-on OpenSSH daemon (container :22, published only on 127.0.0.1:<ephemeral>, key-authed by a per-box ed25519 key). This gives docker boxes a real SSH surface, which powers:

  • agentbox open <box> — live sshfs-mount of /workspace at ~/.agentbox/mounts/<box>/ + Finder reveal (was an rsync copy).
  • agentbox open <box> --in codex — add the box to the Codex app as an SSH connection.
  • "Open in Finder" launcher in the Hub Apps card and the Tray Open In… submenu, shown only for SSH-capable boxes.

How

  • Image: openssh-server + agentbox-sshd-start (root, exec -d, idempotent; writes a hardened sshd_config.d drop-in, ssh-keygen -A). Staged via stage-runtime.mjs. No EXPOSE 22 (publish via -p; OrbStack keeps its single EXPOSE on 6080).
  • Provider (packages/sandbox-docker/src/ssh.ts): mint per-box key → install authorized_keys → launch sshd on create; re-resolve the ephemeral host port on start (mirrors the VNC/web port handling). mintSshKey lifted to @agentbox/sandbox-core (shared with Hetzner). Target recorded on top-level BoxRecord.ssh {host,user,identityFile,port}; syncAgentboxSshConfig emits a Port line.
  • open: gated on SSH_MOUNT_PROVIDERS (docker/hetzner/daytona). Vercel/E2B (no SSH) fail fast with a readable pointer to agentbox download; legacy docker (no sshd) keeps the rsync fallback.
  • Finder launcher: finder is now a first-class open --targets --json entry (providers: SSH_MOUNT_PROVIDERS), consumed by the Hub card + Tray menu (Tray change shipped separately to madarco/agentbox-tray).
  • doctor: optional (warn-not-fail) sshfs + macFUSE checks.

Security: sshd is loopback-only (verified 127.0.0.1:<port>, not 0.0.0.0), key-only auth, no root login, AllowUsers vscode.

Verified

Live docker box: sshd up, port on 127.0.0.1 only, ssh <box> logs in + reads /workspace, SFTP transport works, restart re-resolves the port + re-syncs ~/.ssh/config, --in codex opens the deep link, destroy cleans up the alias + key. Hub API round-trips finder; Tray relaunched with the item. Full monorepo build green; pnpm lint + affected vitest pass.

Note: the sshfs mount itself needs host macFUSE + sshfs (brew install macfuse sshfs) — surfaced as optional doctor checks + a readable error when absent.


Note

Medium Risk
Changes the default open path and adds container sshd plus SSH config/state migration; exposure is limited to loopback key-only auth, with legacy docker and explicit failures for providers without SSH.

Overview
Docker boxes now ship an in-box OpenSSH daemon (loopback-published, per-box ed25519 key) so agentbox open live-mounts /workspace via sshfs at ~/.agentbox/mounts/<box>/ instead of rsyncing a snapshot. Legacy docker boxes without sshEnabled still use the old export path; Vercel/E2B fail fast with a pointer to agentbox download.

SSH state moves to top-level BoxRecord.ssh (with optional port), syncAgentboxSshConfig writes Port for docker, and reads backfill cloud.ssh. --in codex and persistent-SSH gating now include docker; finder is a first-class Open In target (SSH_MOUNT_PROVIDERS) in the CLI, Hub, and tray surfaces.

Supporting changes: shared mintSshKey in @agentbox/sandbox-core, refreshBoxSshd on start/open, optional doctor checks for sshfs / macFUSE, image staging for agentbox-sshd-start, and doc updates for the new default open behavior.

Reviewed by Cursor Bugbot for commit 6ac4225. Configure here.

smoke test added 2 commits July 8, 2026 14:28
…aunchers

Every docker box now runs an always-on OpenSSH daemon bound to container :22,
published only on 127.0.0.1:<ephemeral> and key-authed by a per-box ed25519 key
(minted under ~/.agentbox/boxes/<id>/ssh/, installed into the box's
authorized_keys). This powers a live-mount `agentbox open` and lets the Codex app
add a box as an SSH connection.

- Image: install openssh-server + agentbox-sshd-start (root, exec -d, idempotent),
  staged via stage-runtime.mjs. No EXPOSE 22 (publish via -p; OrbStack keeps 6080).
- Provider: publish :22, mint key, install authorized_keys, launch sshd on
  create + re-resolve the ephemeral host port on start (mirrors the VNC/web ports).
  New packages/sandbox-docker/src/ssh.ts (setUpBoxSshd/launchSshdDaemon/
  installAuthorizedKey). mintSshKey lifted to @agentbox/sandbox-core (shared with
  Hetzner). Record lands on top-level BoxRecord.ssh {host,user,identityFile,port};
  syncAgentboxSshConfig emits a Port line.
- open: `agentbox open` sshfs-mounts /workspace for SSH-capable providers
  (SSH_MOUNT_PROVIDERS = docker/hetzner/daytona); vercel/e2b fail fast with a
  readable pointer to `agentbox download`; legacy docker (no sshd) keeps the rsync
  fallback. `--in codex` now works for docker.
- Finder launcher: `finder` is a first-class open target (open --targets --json),
  gated to SSH_MOUNT_PROVIDERS, surfaced in the Hub "Apps" card and the Tray
  "Open In…" submenu.
- doctor: optional (warn-not-fail) sshfs + macFUSE checks.
- Docs updated (features.md, access-your-box/cli/local-docker mdx).
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agentbox-web Skipped Skipped Jul 8, 2026 2:09pm

Request Review

Comment thread packages/sandbox-core/src/ssh-config.ts
Comment thread apps/cli/src/commands/open.ts Outdated
… box

Address two Cursor Bugbot findings on the SSH move:

- readState now backfills top-level `box.ssh` from a legacy `box.cloud.ssh`
  on read, so an already-created box (e.g. Hetzner) keeps its managed
  ~/.agentbox/ssh/config alias — syncAgentboxSshConfig only reads `box.ssh`
  and would otherwise drop it until the box's next start.
- `agentbox open` / `--in codex` on an already-running docker box now
  refreshBoxSshd() before mounting: a `docker restart` (or docker daemon
  restart) outside `agentbox start` reallocates the ephemeral -p 0:22 host
  port, leaving the recorded Port stale. Extracted the start-path sshd
  re-resolve into a shared, idempotent refreshBoxSshd() in lifecycle.ts.

Tests: readState backfill (+ no-clobber) in state.test.ts.
@madarco

madarco commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

bugbot run

Comment thread apps/cli/src/commands/open.ts
Comment thread apps/cli/src/commands/open.ts
Comment thread packages/sandbox-docker/src/create.ts Outdated
/* best-effort */
}
}
return box;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale SSH after port loss

Low Severity

refreshBoxSshd returns unchanged when publishedHostPort is null, without clearing a persisted box.ssh or re-syncing config. If the container no longer has an SSH publish (or resolution fails) while state still holds an old loopback port, ~/.ssh/config keeps a dead Port until something else rewrites state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 112d28c. Configure here.

…odex legacy msg

Second Cursor Bugbot pass:

- create + refreshBoxSshd only record `box.ssh` / re-sync ~/.ssh/config when sshd
  actually bound (`up`), not merely when Docker published the -p 0:22 host port —
  avoids advertising an alias that points at a dead loopback port.
- `agentbox open --unmount` is handled up front (provider-independent), so a
  legacy docker box no longer falls through to the rsync path instead of tearing
  down its sshfs mount.
- `--in codex` on a docker box that predates the in-box sshd now fails with a
  clear "recreate the box" message instead of a confusing "sshd may have failed".

(The re-flagged "missing cloud.ssh migration" is already handled by the readState
backfill added in the previous commit + its tests.)
@madarco

madarco commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

bugbot run

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ac4225. Configure here.

* changes across restart, and `agentbox open`/start re-syncs `~/.ssh/config`).
*/
export const PERSISTENT_SSH_PROVIDERS: readonly string[] = ['hetzner'];
export const PERSISTENT_SSH_PROVIDERS: readonly string[] = ['docker', 'hetzner'];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codex offered for legacy docker

Medium Severity

Adding docker to PERSISTENT_SSH_PROVIDERS makes open --targets and the Hub treat every docker box as Codex-eligible, but openInCodex rejects pre-sshd docker boxes via sshEnabled. Legacy boxes still show an Open in Codex control that fails at click time.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6ac4225. Configure here.

@madarco madarco merged commit ef879bf into nightly Jul 8, 2026
4 checks passed
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.

1 participant