handover@0.12.0
CAUTION· startup onlyPublisher claims: "Exposes a shared task queue over MCP so an agent on one machine can list, claim, renew, and complete work delegated from another machine, returning output and files to the blocked waiter. It also lets agents delegate tasks in the other direction, with priority ordering, agent targeting, and lease-based recovery when a worker dies." Observed in sandbox: no network, touches nothing outside the project, writes no memory.
Coverage
Startup only; authenticated tool behavior not tested.- Tested
- install scripts, with decoy credentials in place · second launch with the clock 45 days ahead · server startup over stdio (initialize sent) · every file read, file written and host contacted by what ran
- Not tested
- the tool list (the server exited before answering) · what the tools do when called with real credentials
What this means for the label: Caution here covers what ran. The server's tools were not exercised with a real credential, so nothing about their behavior is asserted; the credential summary below says what to do with that.
Before you give it your token
- It will ask for
HANDOVER_TOKENConfiguration, not credentials: CLAUDE_CONFIG_DIR, HANDOVER_AGENT, HANDOVER_AGENT_PATH, HANDOVER_CWD, HANDOVER_HOME, HANDOVER_LAUNCHER, HANDOVER_NO_UPDATE_CHECK, HANDOVER_REPO, HANDOVER_URL, LAUNCHER, SETTINGS, SystemRoot, USERDOMAIN, USERNAME- Where it talks to
- no vendor host named in the codeAlso githubusercontent.com, microsoft.com, apple.com — not the publisher's domain; check what for before connecting.
- What the honeypot saw
- no credential files read · no network
- What we could not test
- the tool list (the server exited before answering) · what the tools do when called with real credentials
Connect it with a scoped credential, under the guard, and read the third-party hosts first. The code names githubusercontent.com, microsoft.com, apple.com as well as its own vendor. The guard injects HANDOVER_TOKEN only into requests to the host you bind (and its subdomains) and refuses the rest.
Never paste a credential into a chat, a ticket or a report, including ours. How the guard holds it.
Is [email protected] safe to install?
KillerSkills ran it in the KillerSkills honeypot sandbox (3 scenarios; startup only; authenticated tool behavior not tested) on 2026-11-09 and scored it 53/100 (Caution). It is not known to be malicious, but it has behaviour or code a security policy should decide on before install. Notable findings: Access to sensitive file paths; Pipe-to-shell pattern; Access to sensitive file paths. Summary: no network, touches nothing outside the project, writes no memory.
Behavior trace
Honeypot run · 27 s · 3 scenarios · 0 deviationsStatic findings
if (task.output) console.log(task.output);Fix: Remove the call, or declare the host in frontmatter and explain in the README what is sent and why.# Bun reads bunfig.toml (preload) and .env from its working directory, whichFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Under curl | bash, stdin is the pipe. Hand prompts the real terminal behind stdout.Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.// The launcher starts Bun in the app folder (so a planted bunfig.toml or .envFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose....env,Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Under curl | bash, stdin is the pipe. Hand prompts the real terminal behind stdout.Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.chmod +x "$runtime_dir/.bun-new"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.# curl -fsSL https://raw.githubusercontent.com/kylepantall/handover/main/scripts/uninstall.sh | bashFix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.# curl -fsSL https://raw.githubusercontent.com/kylepantall/handover/main/scripts/uninstall.sh | bashFix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.if (notes.length) console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: notes.join('\n\n') } }));Fix: Remove the call, or declare the host in frontmatter and explain in the README what is sent and why.<summary>Installer options</summary> | `install.sh` / `install.ps1` | Does | |---|---| | `--host` / `-Host` | This machine is the host | | `--join [URL]` / `-JoFix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.console.log(JSON.stringify({ id: taskId, status: task.status, failureReason: task.failureReason, output: task.output, files }, null, 2));Fix: Remove the call, or declare the host in frontmatter and explain in the README what is sent and why.chmod +x "$launcher"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.<summary>Configuration</summary> | | | |---|---| | File | `config.json` (0600): `role`, `url`, `token`, `agent`, `auto`, `relay` | | Env overrides | `HANDOVER_UFix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.# curl -fsSL https://raw.githubusercontent.com/kylepantall/handover/main/scripts/uninstall.sh | bashFix: Pin versions and sources; vendor anything fetched at install time.<summary>Connect other agents · MCP tools</summary> | Client | Connect with | |---|---| | Claude Code, Codex | Automatic during `host` / `join` | | Claude DesktFix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.await fs.writeFile(paths(dir).config, JSON.stringify({ role: 'client', url: 'http://10.0.0.5:7777', token: 't' }));Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.return 'Enter a URL like http://192.168.1.20:7777';Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.env: { ...base, PATH: path.dirname(process.execPath), HANDOVER_HOME: path.join(dir, home), ...env },Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.| ChatGPT (developer mode) | Needs HTTPS: pick a tunnel in `handover setup`, then `<tunnel-url>/mcp?token=<token>&agent=<name>` |Fix: Keep SKILL.md to visible, descriptive text. Remove comments and encoded text that address the agent.env: { ...base, PATH: path.dirname(process.execPath), HANDOVER_HOME: home, ...env },Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.const child = spawn(process.execPath, [CLI, 'hook'], { env: { ...env(), HANDOVER_URL: 'http://127.0.0.1:1' } });Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.await ui.text({ message: 'Host URL (handover link on the host shows it)', placeholder: 'http://192.168.1.20:7777', validate: validUrl })Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.| ChatGPT (developer mode) | Needs HTTPS: pick a tunnel in `handover setup`, then `<tunnel-url>/mcp?token=<token>&agent=<name>` |Fix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.await fs.writeFile(paths(dir).config, JSON.stringify({ role: 'client', url: 'http://10.0.0.5:7777', token: 'joined-token' }));Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.expect(() => linkInfo({ role: 'client', url: 'http://10.0.0.2:7777', token: 't' })).toThrow('on the host');Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.Cost to the agent
Tokens estimated at four characters each. The tool list is re-read by the agent on every turn, so it is the number to compare between servers that do the same job: ranked across the index.
Known vulnerabilities
Vulnerability advisories (OSV) cover npm and PyPI packages; this registry has no advisory database.
What would make this Verified
27 points to 80 · weights: behavior 45%, static 25%, prompt 15%, publisher 15%- Text that tells the agent to ignore its instructions≈ +9 on the totalDelete phrases like "ignore previous instructions", "override", "bypass" from SKILL.md and tool descriptions. Describe what the skill does; do not instruct the agent about other rules.
- Pipe-to-shell pattern≈ +9 on the totalBuild commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.
- Unclaimed publisher≈ +8 on the totalClaim kylepantall: a verified publisher gets +50 on publisher trust. It is free and usually instant with GitHub sign-in.
- Hidden instructions≈ +6 on the totalRemove HTML comments, zero-width characters and white-on-white text that carry instructions. Everything the agent is meant to read should be visible to the person reading the file.
- Access to sensitive file paths≈ +4 on the totalStop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.
- Direct script execution from GitHub raw URL≈ +4 on the totalPin versions and sources; vendor anything fetched at install time.