Skip to content

fix(sandbox): pin ssh known_hosts to sandbox id - #76

Merged
BhautikChudasama merged 1 commit into
mainfrom
fix/ssh-hostkeyalias-per-sandbox
Aug 21, 2026
Merged

fix(sandbox): pin ssh known_hosts to sandbox id#76
BhautikChudasama merged 1 commit into
mainfrom
fix/ssh-hostkeyalias-per-sandbox

Conversation

@pratikbin

Copy link
Copy Markdown
Contributor

Problem

createos sandbox editor --via tunnel writes HostName 127.0.0.1 for every sandbox. All sandboxes therefore share a single known_hosts identity, [127.0.0.1]:22.

The first sandbox records its host key. The next sandbox presents a different key on the same entry, and ssh reports:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Offending ED25519 key in ~/.ssh/known_hosts_createos:2

OpenSSH downgrades this to a warning when public-key auth succeeds, so the CLI's own flows keep working and the bug stays invisible. Stricter clients do not. They treat a changed host key as an active attack and refuse to connect.

That makes the second CreateOS sandbox unusable as a remote host for those clients. We hit it while integrating CreateOS sandboxes into Orca, whose SSH relay has a dedicated host-key verification subsystem.

VPN mode has the same defect with a longer fuse: it keys on the overlay IP, and overlay IPs are recycled between sandboxes.

Fix

Add HostKeyAlias <sandbox-id> to both the tunnel and VPN blocks. OpenSSH then keys known_hosts on the sandbox id, which is unique per sandbox and stable across pause/resume, instead of on a shared address.

 Host sb-01m0hp7v5vtdnj2pwpj25drhwe my-box
     HostName          127.0.0.1
+    HostKeyAlias      sb-01m0hp7v5vtdnj2pwpj25drhwe
     Port              22
     User              root

One line per transport. No behaviour change for a single sandbox, and no client-side change needed — HostKeyAlias is standard OpenSSH and is surfaced through ssh -G, so tools that resolve config that way pick it up automatically.

Test plan

  • go build ./... passes.
  • go test ./cmd/sandbox/ passes.
  • New cmd/sandbox/editor_test.go covers the previously untested renderSSHBlock:
    • both transports emit the pin,
    • two sandbox ids produce two distinct host-key identities (the actual regression),
    • an unknown mode still errors.
  • Reproduced live against sb-01m0hp7v5vtdnj2pwpj25drhwe; the offending entry was a prior sandbox at known_hosts_createos:2.

Note for reviewers

Existing ~/.ssh/known_hosts_createos files still hold the old 127.0.0.1 entries. They are harmless once this lands, because lookups move to the alias. Deleting that file is safe if you want a clean slate.

Tunnel mode writes `HostName 127.0.0.1` for every sandbox, so all
sandboxes share one known_hosts identity. After the first box is
recorded, the next one presents a different host key on the same
`[127.0.0.1]:22` entry and ssh reports REMOTE HOST IDENTIFICATION HAS
CHANGED. OpenSSH downgrades that to a warning when public-key auth is
used, but stricter clients treat a changed host key as an attack and
refuse the connection outright. Orca's SSH relay is one of them, which
makes a second CreateOS sandbox unusable as a remote host.

VPN mode has the same defect with a longer fuse: it keys on the overlay
IP, and those are recycled between sandboxes.

Add `HostKeyAlias <sandbox-id>` to both blocks. OpenSSH then keys
known_hosts on the sandbox id, which is unique and stable across
pause/resume, instead of on a shared address.

Reproduced against sb-01m0hp7v5vtdnj2pwpj25drhwe while integrating
CreateOS sandboxes as Orca remote hosts.
@BhautikChudasama
BhautikChudasama merged commit b6f98bb into main Aug 21, 2026
1 check passed
@BhautikChudasama
BhautikChudasama deleted the fix/ssh-hostkeyalias-per-sandbox branch August 21, 2026 08:55
pratikbin added a commit that referenced this pull request Aug 25, 2026
* feat(sandbox): add Orca per-workspace environment

Add `createos setup orca`, which lets Orca run a workspace on a
disposable sandbox instead of the user's laptop. The command has two
halves: a human one that checks prerequisites and prints install
instructions, and a hidden `--recipe` one that Orca calls for each
lifecycle phase, selected by ORCA_VM_MODE.

Create provisions a microVM, wires SSH through the gateway, waits for
sshd, then packs and uploads the working tree and checks it out on a
branch named after the workspace. The checkout is pushed rather than
cloned, so no git token reaches the sandbox and private repositories
need no extra setup; uncommitted edits travel with it.

Suspend and resume are not declared. SSH does not reliably come back
after a resume, and Orca requires the pair or neither.

Coding agents are opt-in via --agents/CREATEOS_AGENTS. Orca passes no
agent identity to a recipe, so this cannot be inferred. Installs run
over SSH rather than the exec API: the two do not share a mount
namespace outside /workspace, so anything the exec API installs is
invisible to the session Orca launches the agent in. Each agent links
into /usr/local/bin and is skipped when already present.

Also pin ControlPath per sandbox in renderSSHBlock. Every tunnel-mode
sandbox resolves to the same HostName/User, so a ControlPath built from
%h/%r/%p -- including a common personal `Host *` default -- collides
across sandboxes, and ssh reuses a stale multiplexed connection to a
different, often destroyed, box. This affects `sandbox editor` today,
independently of Orca. %n is the one token unique per sandbox. Same
family as #76.

probeSSH takes the wait as a parameter so the recipe can allow longer
than the editor's default for a cold box.

* fix(sandbox): satisfy repo lint rules in orca recipe

errcheck runs with check-blank, so blank-assigned errors need an
explicit nolint and a reason. Reuse the outer err instead of shadowing
it, and drop the client argument orcaInstallAgents stopped using when
agent installs moved from the exec API to SSH.

No behaviour change.

* refactor(sandbox): move setup under the sandbox group

`createos setup orca` becomes `createos sandbox setup orca`. The command
only ever configures sandboxes, so it belongs in the sandbox tree rather
than as a second top-level group beside it.

root.go is no longer touched by this feature: the group registers itself
through NewSandboxCommand like every other sandbox subcommand, and
NewSetupCommand becomes unexported.

Document the shape in CLAUDE.md, since a harness integration is not like
the other commands: it has a human half and a hidden machine half, the
machine half owns stdout, and the command string is duplicated in a
plugin that lives in another repo.
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