{"slug": "i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them", "title": "I built a way for coding agents to use secrets with O seeing them", "summary": "Developer pdumicz released Secret Shuttle, a local bridge that lets AI coding agents use secrets like API keys without ever seeing the plaintext values. The tool stores secrets in a local daemon vault and exposes only references and metadata to agents, closing a security gap in existing secret management tools. Currently in beta, it supports Claude Code, Codex, Cursor, and Copilot.", "body_md": "Reading this as an AI coding agent?Your starting point is[skills/secret-shuttle/SKILL.md](raw URL:`https://raw.githubusercontent.com/pdumicz/secret-shuttle/main/skills/secret-shuttle/SKILL.md`\n\n). The SKILL is your operational manual; this README is for humans.\n\nLet AI agents use secrets without seeing them.\n\n[ ▶ Walk through the interactive demo →](https://pdumicz.github.io/secret-shuttle/demo/?scene=0) · opens on the one-approval\n\n`provision`\n\nflow, then a 9-scene click-through of the low-level mechanics — a dev shipping a Stripe webhook secret to Vercel prod without ever seeing the value.\n\nStatus: 0.5.0 — beta.The architecture has been through multiple bursts of adversarial security review with fixes shipped at each gate. Not yet independently audited; recommend test accounts and rotating tokens until that audit lands. Suitable for development workflows and prototype deployments.\n\nSecret Shuttle is a local bridge that lets coding agents — Claude Code, Codex, Cursor, browser-using agents — capture, generate, store, compare, and inject secrets through browser and CLI workflows. The agent sees only refs like `ss://stripe/prod/STRIPE_WEBHOOK_SECRET`\n\n, fingerprints, field metadata, and status — never the raw value.\n\nThose tools sync secrets across environments — they assume a human or a CI runner is the consumer. Secret Shuttle assumes an AI coding agent is the consumer, and treats every plaintext touch as a leak vector.\n\n| Tool | Where secrets live | Who sees plaintext | Agent-aware? |\n|---|---|---|---|\n| Doppler / Infisical | Cloud vault | Anyone with read access (incl. agents querying it) | No — sync model |\n| 1Password CLI | OS keychain | Caller process; `op read` writes to stdout |\nNo |\n| Vercel envs (et al.) | Vendor backend | Engineers via dashboard; build runners via env | No |\nSecret Shuttle |\nLocal daemon vault | Only the daemon's child processes (templates) | Yes — agent sees only refs |\n\nIf your secrets already live in a sync tool and an agent never touches them, you don't need Secret Shuttle. If you have an agent writing code that needs to ship secrets to Vercel/GitHub/etc, and you want the agent to do that without the bytes entering its context — that's the gap this closes.\n\n```\nnpx secret-shuttle init\n```\n\nThis starts the local daemon and walks you through creating a vault passphrase in a local web window that only the daemon owns — the CLI never reads it. (Touch ID isn't a first-run prompt: it's how *later* unlocks work once the vault key is enrolled in the OS keychain. `init`\n\nenrols the keychain by default when it creates the vault — pass `--no-keychain`\n\nto opt out, or run `secret-shuttle keychain enable`\n\nlater.) After `init`\n\ncompletes the daemon is running and you are ready to use the CLI or hand it to an agent.\n\nIf you're configuring an agent to use Secret Shuttle, paste this raw skill URL into the agent (it's the canonical operating manual):\n\n```\nhttps://raw.githubusercontent.com/pdumicz/secret-shuttle/main/skills/secret-shuttle/SKILL.md\n```\n\nIf you have the CLI installed locally, run one of these from your project root and the platform-specific instructions file is written for you:\n\n```\nsecret-shuttle agent install claude    # → .claude/skills/secret-shuttle/SKILL.md\nsecret-shuttle agent install codex     # → AGENTS.md snippet (marker-managed)\nsecret-shuttle agent install cursor    # → .cursor/rules/secret-shuttle.mdc\nsecret-shuttle agent install copilot   # → .github/copilot-instructions.md snippet (marker-managed)\nsecret-shuttle agent print-skill-url   # → the raw URL (one line, paste it)\n```\n\nSnippet targets (AGENTS.md, .github/copilot-instructions.md) wrap the Secret Shuttle block in `<!-- secret-shuttle:begin -->`\n\n/ `<!-- secret-shuttle:end -->`\n\nmarkers — re-running `agent install`\n\nonly replaces the marked block, never the surrounding content.\n\n```\nAgent CLI (untrusted client)\n        |\n        | localhost HTTP, bearer token from ~/.secret-shuttle/daemon-socket.json\n        v\nSecret Shuttle daemon\n  - vault key (in memory only, after passphrase unlock through web UI)\n  - approval grants (single-use, 2-min TTL, bound to action/ref/domain/target/field/template)\n  - browser owner — talks raw CDP over a pipe\n  - filtered CDP WebSocket proxy exposed to the agent\n  - safe command-template runner (no shell, no arbitrary commands)\n```\n\nThe daemon owns every secret moment. The agent sees refs and status, never raw values, never the raw Chrome CDP URL, never the vault key.\n\n**CLI templates (most reliable).** Push a secret to Vercel / GitHub Actions / Cloudflare / Supabase — the value goes to the vendor's own CLI, never through the agent. This is the path the hero demo above shows.**Universal browser handoff (experimental).** Your agent drives*any*vendor portal with its normal browser tool, and hands off only the secret moment — it marks the field, the daemon types/reads it under blind mode with the agent's view severed. No per-vendor recipe; it works on a portal nobody integrated.\n\nThe browser path is early (v0.5, best-effort, pending real-page verification): the guarantee is \"the agent never sees the plaintext,\" **not** \"a hostile destination page can't exfiltrate a secret you enter.\" See [SECURITY.md](/pdumicz/secret-shuttle/blob/main/SECURITY.md); for untrusted destinations, prefer the CLI path.\n\n```\nsecret-shuttle provision --secret INTERNAL_CRON_SECRET \\\n  --from random_32_bytes \\\n  --environment production \\\n  --to vercel:production\n# (production secret — approve in the window the daemon opens)\n\nsecret-shuttle secrets list --env production\nsecret-shuttle secrets get-ref ss://local/prod/INTERNAL_CRON_SECRET\nsecret-shuttle audit --since=1d\n```\n\nFor the full browser walkthrough see [examples/stripe-to-vercel/walkthrough.md](/pdumicz/secret-shuttle/blob/main/examples/stripe-to-vercel/walkthrough.md).\n\n```\nsecret-shuttle template list\nsecret-shuttle template run vercel-env-add \\\n  --ref ss://stripe/prod/STRIPE_SECRET_KEY \\\n  --param name=STRIPE_SECRET_KEY \\\n  --param environment=production\n```\n\nTemplates run vetted binaries with `shell: false`\n\n, absolute paths only, and never echo stdout/stderr back to the agent.\n\n- TypeScript CLI distributed as\n`secret-shuttle`\n\n- Local daemon with bearer-authenticated HTTP API on 127.0.0.1\n- Passphrase-derived envelope around the vault master key (scrypt + AES-256-GCM)\n`ss://source/env/name`\n\nrefs- Generate, capture (focused field / selection), inject, compare — all routed through the daemon\n- Inject runs inside a daemon-managed blind window (no manual\n`blind start`\n\n) - Agentic blind transactions:\n`inject-submit`\n\nwrites a stored secret into a marked field, clicks the marked submit control, waits for the approved success marker, and proves the raw secret is absent from every daemon-observable surface before auto-resuming.`reveal-capture`\n\nclicks a marked reveal control, captures the now-visible bytes from a marked field or ancestor container, hides them, and writes them to the vault only after the absence proof passes `secret-shuttle browser mark focused|pick --as <label>`\n\nrecords fields/controls before blind mode by opaque label; only non-secret element metadata is stored- Vault-keyed HMAC fingerprints; production\n`compare`\n\nis approval-gated + rate-limited - Fail-closed domain policy (empty allow-list = injectable nowhere); approvals show the scope\n- Approval-integrity invariant: scope params with leading/trailing whitespace are rejected, so the destination the human approves always matches the argv that actually executes\n`secret-shuttle status`\n\nhealth-check (daemon, vault, browser, policy, local files, agentic-flows availability)- Daemon bearer token is scrubbed from the daemon and all child process envs\n- Approval UI with one-shot, context-bound grants for production actions\n- Daemon-owned Chrome over\n`--remote-debugging-pipe`\n\n- Filtered WebSocket CDP proxy that blocks screenshots, DOM, accessibility, runtime, console, log, and network-body reads during blind mode\n- Built-in templates (stdin delivery):\n`vercel-env-add`\n\n,`github-actions-secret-set`\n\n,`cloudflare-secret-put`\n\n- Built-in template (daemon-owned\n`0600`\n\nenv-file delivery with crash-safe startup + periodic sweep):`supabase-edge-secret-set`\n\n`secret-shuttle agent install <claude|codex|cursor|copilot>`\n\nwrites the canonical Secret Shuttle skill into your project;`secret-shuttle agent print-skill-url`\n\nprints the raw GitHub URL for any agent that accepts a remote skill URL- Exact-by-default domain matching (\n`*.example.com`\n\nfor wildcards) - Migration command:\n`secret-shuttle migrate secure-vault`\n\n- OS-keychain master-key storage (\n`secret-shuttle keychain enable|disable|status`\n\n) —`init`\n\nenrols the vault master key in the OS keychain by default when it creates the vault (opt out with`--no-keychain`\n\n); these subcommands enable/disable/inspect it afterwards, so later unlocks can use the system keychain / Touch ID instead of re-entering the passphrase `secret-shuttle secrets rotate <ref>`\n\n— generates a fresh secret and marks the old ref`rotating`\n\n; you then re-push the new value to its destinations and delete the old ref (it does not yet auto-re-push to existing bindings)`secret-shuttle import --env-file <path>`\n\n— import secrets from a`.env`\n\nfile into the vault`secret-shuttle secrets delete <ref>`\n\n— remove a secret from the vault\n\n- Hardware-backed key storage (HSM / Secure Enclave) — note: OS-keychain key storage\n*does*ship (see`keychain enable`\n\nabove); this entry is only the hardware-backed tier - Team vaults, cloud sync, MCP server, browser extension\n- Template argv-vs-\n`--help`\n\ngates ([P2b] PENDING): the shipped templates' argv vectors have not been verified against the current`gh`\n\n/`wrangler`\n\n/`supabase`\n\n`--help`\n\noutput on a per-release basis\n\nThe honest steady state:the first time you use a provider, you log in once in the Secret Shuttle browser. After that, one approval ships everything for providers with a recipe (see the coverage matrix above). Providers whose secrets can't be revealed (OpenAI/Anthropic) are human-paste — you supply a key you created; the daemon never reveals it.\n\nWhat's automated, by provider and direction. Browser recipes drive the page hands-off (one approval); CLI templates push via the vendor CLI. \"Real-page verified\" is a human-attested dogfood date (CI has no provider creds).\n\n| Provider | Direction | Mechanism | Status | Real-page verified | Notes |\n|---|---|---|---|---|---|\n| Stripe | capture (secret key) | browser recipe | 🆕 this increment | (set on dogfood) | revealable in dashboard |\n| Supabase | capture (service_role) | browser recipe | ⬜ planned | — | revealable in settings/api |\n| OpenAI / Anthropic | capture | human-paste | n/a | n/a | create-once; cannot be revealed |\n| Vercel | inject (env) | browser recipe and CLI (`vercel-env-add` ) |\nCLI shipped; browser recipe URL-configurable — set `url_params: { team, project }` in yml (selectors still best-effort, pending dogfood) |\n— | CLI push is the robust, project-general default. The browser recipe's URL is now templated (`vercel.com/{team}/{project}/settings/environment-variables` ); the user supplies `url_params` per destination in their yml. Selectors are still best-effort and pending real-page dogfood verification (`verified_against_real_page: \"2026-06-01-needs-dogfood\"` ); URL addressability is independent of selector verification. |\n| GitHub Actions | inject (secret) | CLI (`github-actions-secret-set` ) |\n✅ shipped | n/a | repo-scoped only |\n| Cloudflare | inject (secret) | CLI (`cloudflare-secret-put` ) |\n✅ shipped | n/a | |\n| Supabase edge | inject (secret) | CLI (`supabase-edge-secret-set` ) |\n✅ shipped | n/a |\n\nThe absence proof is fail-closed: the daemon won't hand the agent back its view unless it can confirm the secret is gone from the page's DOM/URL (otherwise it stops with `manual_recovery_required`\n\n). That guarantees the **agent** never sees the plaintext — it does **not** hook network/clipboard/storage, so it can't stop a hostile destination page from transmitting a value you deliberately entered. Only run browser flows against pages you trust ([SECURITY.md](/pdumicz/secret-shuttle/blob/main/SECURITY.md)). Every new provider is a new row.\n\n- Deferred provider templates (\n`github-actions-env-secret-set`\n\n,`github-actions-org-secret-set`\n\n,`railway-variable-set`\n\n,`netlify-env-set`\n\n,`clerk-env-set`\n\n) — see[docs/templates-deferred.md](/pdumicz/secret-shuttle/blob/main/docs/templates-deferred.md)for the reason and re-open criteria - Signed desktop binaries\n- Secret export workflows (rotation and\n`.env`\n\nimport ship; export does not)\n\n[skills/secret-shuttle/SKILL.md](/pdumicz/secret-shuttle/blob/main/skills/secret-shuttle/SKILL.md)— the canonical agent operating manual[docs/security-model.md](/pdumicz/secret-shuttle/blob/main/docs/security-model.md)[docs/threat-model.md](/pdumicz/secret-shuttle/blob/main/docs/threat-model.md)[docs/cli-reference.md](/pdumicz/secret-shuttle/blob/main/docs/cli-reference.md)[docs/architecture.md](/pdumicz/secret-shuttle/blob/main/docs/architecture.md)[docs/roadmap.md](/pdumicz/secret-shuttle/blob/main/docs/roadmap.md)\n\nFor contributors and anyone who wants to hack on the code:\n\n```\ngit clone https://github.com/pdumicz/secret-shuttle.git\ncd secret-shuttle\nnpm install\nnpm run build\nnpm link\nsecret-shuttle daemon start\nsecret-shuttle unlock\n```\n\n`unlock`\n\nopens a local web window — you enter the passphrase there. The CLI never reads it.\n\nMIT", "url": "https://wpnews.pro/news/i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them", "canonical_source": "https://github.com/pdumicz/secret-shuttle", "published_at": "2026-07-01 16:17:14+00:00", "updated_at": "2026-07-01 16:20:18.365661+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools"], "entities": ["Secret Shuttle", "Claude Code", "Codex", "Cursor", "Copilot", "Vercel", "Stripe", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them", "markdown": "https://wpnews.pro/news/i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them.md", "text": "https://wpnews.pro/news/i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them.txt", "jsonld": "https://wpnews.pro/news/i-built-a-way-for-coding-agents-to-use-secrets-with-o-seeing-them.jsonld"}}