| name | blindfold |
| description | Use Blindfold (Terminal 3 TDX enclave wrapper) to seal and use API keys safely. Invoke when the user mentions sealing/sealing a key, asks "how do I protect my API key", pastes a credential into chat, or asks for help with secrets in this project. Always prefer the no-paste workflow — propose commands the user runs in their own terminal, verify by fingerprint, never write keys to files. |
Blindfold skill — the rulebook
You're working in (or with) Blindfold — a Terminal 3 TDX-enclave wrapper that keeps AI-agent API keys un-leakable. Read current_status.md, process.md, usage_by_claude.md, and vicky.md for context; this file is the agent-side rulebook that decides how you behave.
When to invoke this skill
- User pastes (or is about to paste) any API key, password, token, or credential.
- User asks "how do I use [provider]" in a way that implies handling a key.
- User asks about
.env, sealing, vaulting, or "where do I put my key".
- You're about to write code that would read
process.env.SOME_PROVIDER_KEY for an outbound call.
- User says "use Blindfold" or "seal it" or "make this safe".
- User has no Terminal 3 tenant yet ("how do I start", "I don't have an account/DID",
doctor shows creds MISSING) → point them at self-serve blindfold signup (see below).
The four rules (mirror of usage_by_claude.md §3)
- R1 — no-paste-into-chat. If a new secret needs sealing, propose the command (
blindfold register --name <KV_KEY>) for the user to run in their own terminal. Do NOT ask them to paste the value into chat. Only use printf 'VALUE' | ... from chat as a fallback, and only after the user explicitly says "go ahead from here".
- R2 — verify by fingerprint, never by value. To check what's sealed:
blindfold sealed. To check what's in .env: npm run env:fingerprint. To check a specific sealed key matches an expected value: npx tsx scripts/test-v5-release.ts <secret_name> (prints first3…last2 (N bytes), never plaintext). Ask the user to paste the output of those commands — that's safe.
- R3 — code uses release-broker pattern. Any code you write that needs a provider key must fetch it from T3 just-in-time via
tenant.contracts.execute("blindfold-proxy", { version: CONTRACT_VERSION, functionName: "release-to-tenant", input: { secret_key: "<name>" } }), use it inside a try { … } finally { /* dropped */ }, and never reference process.env.<provider>_API_KEY. Reference templates: examples/grok-via-blindfold.ts (HTTPS) and scripts/smtp-with-blindfold.ts (non-HTTP).
- R4 — propose
.env cleanup after every successful seal. The sealed copy is canonical; the .env copy is leak surface. Exception: T3N_API_KEY itself stays in .env — it's the root credential, can't be sealed (chicken-and-egg).
Command kit (every output is safe to share)
| Command | Purpose | Safe to paste output? |
|---|
blindfold signup --email <addr> | self-serve: mint a new testnet tenant (key local, email-verified) | ⚠ tell user to run it (prompts for the emailed code) |
blindfold doctor | mode + cred presence (yes/no) | ✅ |
blindfold verify | T3 round-trip status | ✅ |
blindfold credit | tenant token/credit balance | ✅ |
blindfold attest [--pin] | verify enclave TDX attestation; --pin gates seal/proxy on the code measurement | ✅ |
blindfold login | store tenant creds in ~/.blindfold (OS keychain for the key) — works from any dir | ⚠ tell user to run it (prompts for key) |
blindfold whoami | show config path, tenant, env, key source (never the value) | ✅ |
blindfold logout | remove stored creds (keychain + ~/.blindfold/config.json) | ✅ |
blindfold sealed | sealed-keys ledger (metadata only, LOCAL) | ✅ |
blindfold audit | reconcile ledger against the ENCLAVE — what's actually usable now | ✅ |
blindfold status | one-glance: mode, tenant health, sealed list | ✅ |
npm run env:fingerprint | .env lines as KEY = first3…last2 (N bytes) | ✅ |
npx tsx scripts/test-v5-release.ts <name> | fingerprint of the released value | ✅ |
npm run dashboard | live HTML dashboard at http://127.0.0.1:8799 | n/a (UI) |
blindfold register --name <K> | interactive seal (no echo) | ⚠ tell user to run in their terminal |
printf 'V' | blindfold register --name <K> | piped seal (value briefly in this process) | ⚠ only if user already pasted value |
blindfold delete --name <K> [--yes] | remove a sealed secret (empties enclave + ledger, re-chained) | ⚠ destructive — confirm first; tell user to run it |
Seal once, use forever (no re-seal per session)
A sealed key lives in the Terminal 3 enclave, not in a session, terminal, or
machine. So the answer to "do I need to re-seal in a new session?" is no —
any new session can use an already-sealed key directly. Don't propose re-running
register for a key that's already sealed.
To use already-sealed keys, a session needs three persistent things (all
survive across sessions — none require re-sealing):
- Tenant creds —
T3N_API_KEY + DID. These authenticate to the enclave
and are what release/proxy require. Ways to provide them:
blindfold signup --email you@x.com (self-serve, the fastest start):
mints a brand-new Terminal 3 testnet tenant with no manual token claim —
generates the tenant key locally (→ OS keychain, never printed), verifies the
email by an emailed code, self-admits, and mints welcome credits. One email
binds to one tenant. Use this when the user has no tenant yet.
- Repo
.env (dev). Exception to R4: they stay in .env; see R4.
blindfold login (v0.2+, product path, for an EXISTING tenant): stores DID + settings in
~/.blindfold/config.json and the tenant key in the OS keychain (v0.3;
macOS Keychain / Linux secret-tool) — so the CLI works from any directory
and the key isn't a readable file. Precedence: process.env > repo .env >
~/.blindfold. State (ledger/usage/egress) also lives in ~/.blindfold.
- Egress already granted for the host you'll call (
blindfold grant --host <host>). The grant is per-tenant and persistent.
- The proxy running if using the HTTP path:
blindfold proxy
(listens on 127.0.0.1:8787). Point the tool at it with base URL
http://127.0.0.1:8787/v1 and key __BLINDFOLD__. (Code paths use the
release-broker instead — see R3.) On a shared machine, run proxy --auth:
it mints a per-session token so only the wrapped agent (not any co-resident
process) can use the proxy — pass it via BLINDFOLD_PROXY_TOKEN /
wrap(client, { token }), or curl -H "x-blindfold-token: <token>".
Before assuming a key is usable, run audit, not sealed. sealed only
reads the LOCAL ledger; audit reconciles it against the enclave (the source of
truth) and marks each key present (usable now) or MISSING (needs a re-seal).
A key in sealed but MISSING in audit is the only case that needs
re-sealing.
What to do, by scenario
"I'm new / I don't have a Terminal 3 tenant yet" (onboarding)
Blindfold is published on npm and onboarding is self-serve — no manual token claim.
Propose (the user runs it in their own terminal):
npm i -g @fiscalmindset/blindfold
blindfold signup --email you@example.com
signup generates the tenant key locally (→ OS keychain, never printed),
verifies the email by an emailed code, self-admits a funded testnet tenant,
and mints welcome credits. Then confirm with blindfold doctor (should show the
tenant active) and blindfold credit (token balance). After that they can
register and use keys. Notes: testnet-only; one email = one tenant (Gmail
+aliases give fresh identities); if the email is already registered, use
blindfold login with that tenant's key instead.
"I want to seal my Stripe key" (or any new credential)
Don't ask for the value. Respond with:
In your own terminal, run:
blindfold register --name stripe_api_key
It'll prompt for the value with input hidden (no echo, no shell history). Paste your sk_live_… there, press Enter. Once done, paste me the output of blindfold sealed so I can verify it landed in the right place.
"I sealed the wrong thing / want to remove a sealed secret"
Use blindfold delete --name <secret> (alias remove). It empties the value in
the enclave (current tenant) and removes the entry from the local ledger,
re-chaining so blindfold audit still passes (the old ledger is backed up).
Propose it for the user to run; it prompts for confirmation (--yes skips).
Critical: if the value they mis-sealed was a real credential (e.g. they put
an API key in the --name field), deleting it here does NOT un-expose it — tell
them to revoke/rotate that key at its provider, then re-seal correctly
(--name is a short label; the value is prompted hidden).
"Seal / use a webhook URL (Discord, Slack, …)"
A webhook is special: the secret is the entire URL (e.g.
https://discord.com/api/webhooks/<id>/<token>), POSTed with no auth header.
Seal it like any secret (register --name webhook_discord_url --from-env …),
then two ways to use it:
- Release path (works everywhere): inject into one command —
blindfold use --name webhook_discord_url --as HOOK -- sh -c 'curl -X POST -H "Content-Type: application/json" -d "{\"content\":\"hi\"}" "$HOOK"'
- Proxy path (agent never holds the URL; contract v0.5.5+): the
webhook
scheme substitutes the sealed URL in the URL inside the enclave. There's a
/discord provider — grant egress (grant --host discord.com), run the proxy,
and POST a JSON body to http://127.0.0.1:8787/discord. See
examples/discord-webhook/.
"Add support for an API that isn't a provider yet" (register a new proxy provider)
If the user wants an API Blindfold doesn't list (unlisted paths get a 404 "no
upstream mapping" — deny-by-default), prefer adding a first-class proxy provider
over the release-broker fallback — the proxy path never returns plaintext, so it's
the stronger option. This is pure code (no secret value), so you can do it end to
end; only the final register (plaintext) is handed back to the user.
- Add a ~5-line entry to
packages/blindfold/src/providers.ts — no enclave/Rust
change is needed as long as the API uses one of the four existing auth schemes
(bearer / basic / sigv4 / webhook). Example for a bearer API (Notion):
{
id: "notion",
prefix: "/notion/",
upstream: (p) => `https://api.notion.com${stripPrefix(p, "/notion/")}`,
secretKey: "notion_api_key",
auth: () => ({ scheme: "bearer" }),
defaultHeaders: { "Notion-Version": "2022-06-28" },
},
- Rebuild (
npm run build in packages/blindfold) so dist/ picks up the entry.
- Hand back the two one-time human steps: seal the key —
blindfold register --name notion_api_key (in their terminal, R1) —
and grant egress: blindfold grant --host api.notion.com.
- The agent then calls
http://127.0.0.1:8787/notion/... with
Authorization: Bearer __BLINDFOLD__; the enclave swaps in the sealed key.
Only touch the Rust contract (contract/src/forward.rs) if the API needs a brand-new
auth scheme none of the four cover — otherwise this stays a TypeScript-only change.
"I already pasted my OpenAI key in chat — seal it"
It's in our chat context now (can't undo). Reduce future surface:
printf 'sk-...' | blindfold register --name openai_api_key
Then propose deleting any .env copy. Note in your response that the chat-context exposure already happened and you can't retroactively fix it — only forward-protect.
"Write me code that calls OpenAI"
Write the release-broker pattern (see examples/grok-via-blindfold.ts). Never import OpenAI from "openai" + apiKey: process.env.OPENAI_API_KEY. Always:
const { value: apiKey } = await tenant.contracts.execute("blindfold-proxy", {
version: CONTRACT_VERSION,
functionName: "release-to-tenant",
input: { secret_key: "openai_api_key" },
}) as { value: string };
try {
} finally { }
"Read my .env to check what's there"
Don't Read .env. Run:
npm run env:fingerprint
Ask the user to paste that output. You get key names + lengths + first/last few chars — enough to identify, never enough to use.
"It's broken — what's wrong?"
Run these in order, asking the user to paste each output:
blindfold doctor
blindfold verify
blindfold sealed
Cross-reference any errors with vicky.md Q6 (keyword-indexed error table).
What this skill must NEVER do
- Write a real plaintext key value to any file, doc, or commit message.
console.log / safeLog a value whose origin is process.env.*_API_KEY or an Authorization header.
- Use
Read on .env when npm run env:fingerprint would do.
- Suggest the user paste a key into chat as a default — always default to "run the command in your terminal".
- Generate code that references
process.env.<provider>_API_KEY for outbound provider calls.
When the user objects to a recommendation
If the user explicitly chooses a less-defensive path ("I know, just seal it from here"), honor it but say so once: "Going to use the piped-stdin path — value will be in this chat's transcript; if you want zero chat exposure, prefer running register in your own terminal." Then proceed with their choice.
Reference files (when in doubt)
| File | What's in it |
|---|
usage_by_claude.md | The user-facing twin of this rulebook; refresh §5 status table when sealing |
process.md | First-time-user walkthrough — copy command shapes from here |
vicky.md | Plain-English Q&A — copy explanations from here when answering questions |
current_status.md | What's working vs blocked right now — quote from here when status is asked |
examples/grok-via-blindfold.ts | The canonical release-broker template for HTTPS APIs |
scripts/smtp-with-blindfold.ts | The canonical release-broker template for non-HTTP protocols |