Uh oh!
There was an error while loading. Please reload this page.
feat(cli): --repo flag clones a template repo and applies spawn.md - #3360
Conversation
spawn <agent> <cloud> --repo user/template Clones https://github.com/user/template.git to ~/project on the VM, parses spawn.md (YAML frontmatter), and applies its custom-setup contract: - `setup`: oauth (open URL + wait for Enter), cli_auth (run on VM), api_key (no-echo prompt → /etc/spawn/secrets, sourced from .bashrc), command (run on VM) - `mcp_servers`: env values stay as ${NAME} placeholders so secrets never end up in the template repo. Replay routes through the existing skills.ts helpers (Claude settings.json, Cursor mcp.json, Codex config.toml) — no `node -e` injection. - `setup_commands`: run inside ~/project When the clone succeeds, the agent launches with `cd ~/project && ...` so the user lands in their template's working directory. Reconnect via `spawn last` replays the same launchCmd. Built-in steps (github auth, auto-update, etc.) stay in the CLI --steps flag — spawn.md only handles custom setup that Spawn doesn't know about natively. Bumps CLI to 1.0.22. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
la14-1
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVE
Commit: 24cbdad
Threat model
The --repo user/template flag explicitly opts the user in to running arbitrary shell from a third-party GitHub repo (setup_commands, cli_auth.command, command.command are all unsandboxed). This is the feature, not a vulnerability — a user invoking spawn claude hetzner --repo X/Y is consenting to let X/Y's spawn.md drive setup. Reviewed under that threat model.
What I verified
- Slug regex
^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$is tight — no shell metacharacters, no@for host override, no?/&for query injection. Clone URL alwayshttps://github.com/<slug>.git. ✅ openBrowseruses argv arrays, not shell —oauth.urlandapi_key.guide_urlcannot trigger RCE via URL-shaped payloads. ✅- TOML writer (
tomlString) escapes\and"correctly for TOML basic strings. ✅ SpawnMdSchemavia valibot rejects unknowntypevalues and missing required fields — malformedspawn.mdreturns null ("ignored with warning"), not thrown. ✅- Parse-loose / validate-strict pattern for the hand-rolled YAML parser:
parseYamlFrontmatternever throws; valibot is the gate. Reasonable given the frontmatter subset used. ✅
Findings
| Sev | File | Issue |
|---|---|---|
| MEDIUM | spawn-md.ts:394 | Deferred shell injection via /etc/spawn/secrets when an api_key value contains " or newline — the file is later sourced from ~/.bashrc, so corruption becomes code execution on next login. Self-inflicted, but the escaping is wrong. |
| LOW | skills.ts:322 | tomlString handles \ and " but not raw newlines — multi-line MCP env values produce invalid TOML for Codex. Functional corruption, not RCE. |
| LOW | spawn-md.ts:270 | /tmp/spawn-capture-${Date.now()} — predictable path, shared-tmp race risk. Inert on single-user VMs. |
| NOTE | spawn.md env: { VAR: "${NAME}" } | Placeholders written literally into agent MCP configs. Claude/Codex MCP clients don't expand env vars in mcp_servers.*.env — users will see literal ${NAME} strings passed to the MCP server. Functional, not security. |
Recommended follow-up (MEDIUM)
Do not write user-supplied values into a shell-sourceable file. Either (a) keep values base64-encoded at rest and decode at source-time via a wrapper, or (b) use a dotenv-style loader with a parser that does not re-interpret quotes. Keeping the value encoded eliminates quote-escape concerns entirely.
Tests
bunx @biomejs/biome check src/— 195 files, no issuesbun test— 2112 pass, 0 fail (+4 new spawn-md tests)bunx tsc --noEmit -p .— 5 pre-existing errors inupdate-check.test.tsanddaytona.ts, all present on main, none introduced by this PR
Per security protocol: MEDIUM + LOW → APPROVE. Filing the escape-injection finding as a follow-up.
-- security/pr-reviewer
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Adds `--repo user/template` to the spawn CLI. Clones the named GitHub repo to `~/project` on the VM, parses `spawn.md` (YAML frontmatter) at the repo root, and applies it as a setup contract before handing off to the agent.
```
spawn --repo user/my-template
```
What spawn.md can declare
/.claude/settings.json`, Cursor → `/.cursor/mcp.json`, Codex → `/.codex/config.toml` (new TOML installer), generic → `/./mcp.json`.Built-in steps (github auth, auto-update, security-scan, etc.) stay on the CLI `--steps` flag — `spawn.md` only handles the custom-setup parts Spawn doesn't know about natively.
Launch behavior
If the clone succeeds, the agent launches with `cd ~/project && <agent.launchCmd>` so the user lands in their template's working directory. `saveLaunchCmd` persists this, so `spawn last` reconnects into the same dir. Invalid slugs / clone failures fall back to the standard launch — no broken-cd footgun.
Test plan
🤖 Generated with Claude Code