Public: anyone can read this; the crew can change it.
A spawned child's own keys and address, FORGE_APP_SSH verbatim, and one sealed, capped API key #747
Check · Review · Land · Live done — Landed and live in production.
Closes #576. **Stacked on #716 (#712), which is stacked on #704 (#575)**: merge those first. This pull's own commit is "A spawned child's own keys and address…".
shipped production is serving it for issue #576 — its issue is already done deploy d5b2fe2
Sign in to join this conversation.
new above
offline
Closes #576. Stacked on #716 (#712), which is stacked on #704 (#575): merge those first. This pull's own commit is "A spawned child's own keys and address…".
The child's own keys. Spawn makes the child's
secret.key(32 bytes, 0600) and SSH host key in the child's own directory, so they exist before first boot. A test compares them with the parent's.Nothing points into the parent. The child's config paths resolve inside its own directory.
parent.jsonholds onlyupstream, name, org, url, keepAccess, with no paths and no secrets. A key sealed in the child cannot be opened with the parent'ssecret.key(tested).Its domain. The child's address is
<name>.<parent's host>, from the placement. It is recorded asurlinparent.jsonand shown on the child's home as "Your forge: maria.forge.test".FORGE_APP_SSHverbatim. A hosted forge getsappSshfromFORGE_APP_SSH(inmain.ts, only when hosted).sshCommandreturns it word for word for the forge's own terminal. The SSH keys page says "The terminal is off on this forge." when there is neither a listener nor a host command.terminalFornow reads the listener port itself, so a host command never hides that an app's terminal is off.The owner's open decision, as designed here. The one credential a spawner may give a child is
modelKey, an API-billed key (sk-ant-api…), with a requiredkeyCapUsddaily cap from $1 to $1000.sk-ant-oat…) is refused: "a person's own plan and never goes to a child". A key without a cap, or a cap without a key, is refused.starter, granted to its organisation's crew, and each app's daily ceiling is the cap.spawntool refusesmodelKey: a secret never passes through an agent's conversation; the error points to the API and the child's Subscriptions page.The path: the person claims the forge and lands on a home that shows "Your forge: maria.forge.test", their app, and (without a key) the line saying the crew cannot start yet. An admin can open SSH keys and read whether the terminal is on and how to reach it.
Removes: nothing. It adds one line on a spawned forge's home and one sentence on the keys page.
Tests:
test/spawn-keys.test.ts(8, in the gate): the oauth token, a missing cap, a cap with no key and a zero cap are refused; MCP refuses a key; the dry run and the answer never show the key; no parent file, ledger or idempotency row holds it, nor any child file in clear text; the child's keys differ from the parent's; the key opens only with the child's key and is granted to the org with a $5 ceiling; the config stays inside the child; the address is recorded and shown; "terminal is off" appears, thenFORGE_APP_SSHis printed verbatim. Spawn, first-boot, child-apps, studio, MCP and apps suites pass.Full gate under the lock: the only failure that was mine, "claim" missing from the reserved names (the
/claimroute from #704), is fixed on #704 and carried up the stack. Still failing: reserved-names onllms.txt, which also fails on main (#705), and the crew.test "assignment" load flake.Looks (a real spawned child, 390 and 1280 px): on the phone home, "Your forge: maria.forge.test" above the cost line, the key note, then Your apps. On the desktop SSH keys page, "The terminal is off on this forge." above the key list.