KeyVeilPlatform

Blind keys for fast agents. Store API keys encrypted, hand agents scoped tokens, and let every call run through a proxy that injects the secret server-side.

The problem

Your agent reads everything you give it.

Pasted into a chat

A key dropped into a chat window to "just get unblocked" now lives in someone's model logs, forever, searchable.

Leaked through tool calls

One printenv or cat .env inside an agent's tool call and the secret is sitting in the transcript.

Committed by a helpful agent

An agent that "helpfully" writes a config file commits your key to the repo — then to every clone, fork, and log.

The one-line answer: the key never leaves the Worker. The agent gets a result, not a string.

How it works

Three boxes. The secret crosses one boundary.

Before / after

Delete the key from the agent's world.

Before — agent can read this

OPENAI_API_KEY=sk-proj-abc123...   # agent can read this

$ printenv | grep OPENAI   # it's in the transcript now

After — agent gets a result, not a string

curl -H "Authorization: Bearer $VEIL_AGENT_TOKEN" \
  -d '{"model":"gpt-4o-mini","input":"summarize this"}' \
  "https://keyveil-api.parithosh.workers.dev/v1/proxy/openai/chat"

Storage

VeilVault

Keys encrypted with AES-256-GCM before they touch the database, wrapped by per-account keys. Dashboards and APIs list names only — values are never returned by default.

Control

VeilScope

Agent tokens limited to named actions, IP ranges, and expiry dates. Each token is shown once; only its hash is stored. Revocation lands on the very next request.

Execution

VeilProxy

Calls arrive with a token, never a key. The gateway checks scope and identity, injects the secret, and returns only the result. Reading a raw value needs the separate secrets:reveal scope — and gets logged.

  • AES-256-GCMencryption at rest
  • 0secret characters in logs
  • 100%of calls audit-logged
  • 1request to revoke everywhere

One blind call, simulated in your browser. Nothing leaves this page.

CLI

Script the whole lifecycle

npm i -g keyveil

1 · Log in

Create an agent key in the dashboard, then store it (mode 600).

$ keyveil login --token tv_live_…
saved to ~/.keyveil/config.json (api: https://keyveil-api.parithosh.workers.dev)

$ keyveil login --status
ok as u_498975c1719ba1d6903734 via agent (key: opencode)
scopes: openai:chat, github:create-repo
api: https://keyveil-api.parithosh.workers.dev

2 · Store secrets

List shows masked previews and last use — never values.

$ keyveil secrets add OPENAI_API_KEY
Value for OPENAI_API_KEY (hidden):
stored OPENAI_API_KEY (encrypted server-side)

$ keyveil secrets list
name            preview   updated_at            last_used
GITHUB_TOKEN    ghp_…9f2c  2026-09-10T08:12Z    2026-09-16T12:31Z
OPENAI_API_KEY  sk-p…f4a2  2026-09-12T19:44Z    2026-09-16T12:33Z

3 · Mint a scoped agent key

The full token prints once. Only its hash is stored.

$ keyveil keys create --name opencode \
    --scopes openai:chat,github:create-repo --ttl 90
created key k_9f2c41ab (tv_live_9f2c41ab) scopes=openai:chat,github:create-repo
SAVE THIS TOKEN NOW — it is shown only once:
tv_live_9f2c41ab7d…

4 · Spend blind

Server injects the secret; you only see the result.

$ keyveil proxy github create-repo -d '{"name":"demo","isPublic":true}'
{
  "url": "https://github.com/you/demo",
  "full_name": "you/demo",
  "private": false
}

Agents

Give your agent the skill, not the secret.

OpenCode

Paste this block into your session. Verbatim from SKILL.md.

You have access to a personal blind secrets gateway. Do NOT ask the user for API keys.

Base: $API_BASE_URL (default https://keyveil-api.parithosh.workers.dev)
Auth: Authorization: Bearer $VEIL_AGENT_TOKEN (already in env, never print it)

Rules:
1. Prefer blind proxy. POST /v1/proxy/<provider>/<action> with JSON args. Use the returned result only.
   - GitHub repo: POST /v1/proxy/github/create-repo {"name":"foo","isPublic":true}
   - OpenAI: POST /v1/proxy/openai/chat {"model":"gpt-4o-mini","input":"..."}
2. Discover first: GET /v1/tools lists what this user allows.
3. Never echo / cat / printenv secrets. Never put secret values in chat, logs, or git. Raw values are only available via GET /v1/secrets/:name with an explicit secrets:reveal scope (terminal use, audit-logged) — proxy calls must never request or expect them.
4. On 401/403/429 stop and say: "veil auth failed / scope denied / IP not allowlisted / rate limited" — do not retry with a different key, do not dump headers.
5. Every proxy call is audit-logged per user. Keep args minimal.
6. Terminal management (human's own terminal): the keyveil CLI (login, secrets, keys, proxy) talks to the same API with $VEIL_AGENT_TOKEN.

Claude Code

Same contract, dropped into CLAUDE.md.

# KeyVeil — blind secrets gateway (paste into CLAUDE.md)

You have access to a personal blind secrets gateway. Do NOT ask the user for API keys.

- Base: $API_BASE_URL (default https://keyveil-api.parithosh.workers.dev)
- Auth: Authorization: Bearer $VEIL_AGENT_TOKEN (already in env, never print it)
- Prefer blind proxy: POST /v1/proxy/<provider>/<action> with JSON args; use the returned result only.
  - GitHub repo: POST /v1/proxy/github/create-repo {"name":"foo","isPublic":true}
  - OpenAI: POST /v1/proxy/openai/chat {"model":"gpt-4o-mini","input":"..."}
- Discover first: GET /v1/tools lists what this user allows.
- Never echo / cat / printenv secrets. Never put secret values in chat, logs, or git. Raw values need secrets:reveal scope (terminal use, audit-logged) — proxy calls must never request them.
- On 401/403/429 stop and report "veil auth failed / scope denied / IP not allowlisted / rate limited". Do not retry with a different key, do not dump headers.

Cursor

Same contract as a .cursorrules entry.

# .cursorrules — KeyVeil blind secrets gateway

- Never ask the user for API keys. A blind secrets gateway is available.
- Base: $API_BASE_URL (default https://keyveil-api.parithosh.workers.dev); Auth: Bearer $VEIL_AGENT_TOKEN (env, never print).
- Use blind proxy only: POST /v1/proxy/github/create-repo {"name","isPublic"} or POST /v1/proxy/openai/chat {"model","input"}. Consume the result JSON; the secret itself is never visible.
- Discover via GET /v1/tools before assuming an action exists.
- Forbidden: echo/cat/printenv of secrets; secrets in chat, logs, git, or generated config files.
- On 401/403/429: stop, report the mapped message (auth failed / scope denied / IP not allowlisted / rate limited), do not retry with another key.

Any tool-using agent

Expose the two proxy tools as functions. Raw values are never a tool.

// Tool definitions for any function-calling agent.
// Auth: Authorization: Bearer $VEIL_AGENT_TOKEN. Base: https://keyveil-api.parithosh.workers.dev
{
  "tools": [
    {
      "name": "keyveil_proxy",
      "description": "Run an allowed provider action blind. The secret is injected server-side; only the result returns.",
      "input_schema": {
        "provider": "github | openai",
        "action": "create-repo | chat",
        "args": "object (e.g. {\"name\": \"demo\", \"isPublic\": true})"
      },
      "endpoint": "POST /v1/proxy/{provider}/{action}"
    },
    {
      "name": "keyveil_tools",
      "description": "List what this user allows. Call first.",
      "endpoint": "GET /v1/tools"
    }
  ],
  "never": ["request secret values", "echo/printenv/cat secrets", "retry 401/403 with another key"]
}

Discovery: GET /v1/tools

Public to any bearer token. Agents call it first to learn exactly what this user allows — no guessing, no prompt archaeology.

ToolInput
github/create-repo{ name, isPublic }
openai/chat{ model, input }

Error contract

Agent builders: these are stable. Parse them, don't retry around them.

StatusMeaningDo
401Missing, invalid, or expired token/grantStop, ask the human to re-issue
403Scope denied (need names it) or IP not allowlistedStop — never try a different key
429Over 60 req/min on this key (Retry-After: 60)Back off, then continue
404Unknown tool — response lists the tools you do haveRe-discover via /v1/tools

Providers

A short honest list.

OpenAI

Live — chat completions

POST /v1/proxy/openai/chat

GitHub

Live — create repos

POST /v1/proxy/github/create-repo

Anthropic

Planned

Messages API through the same blind path.

Stripe

Planned

Scoped payment intents, never the secret key.

+ Request a provider →

We add what agents actually spend.

Security

Enterprise controls, on from the first request

Encryption, concretely

AES-256-GCM. Each account gets its own data key, wrapped by a master KEK held only as a Cloudflare secret (ENCRYPTION_KEK). KV holds ciphertext blobs (u:<user>:s:<NAME>); D1 holds names, masked hints, and audit rows — never plaintext.

Tokens that can't leak twice

Agent tokens (tv_live_…) print once; only a SHA-256 hash is stored. Expiry is mandatory thinking (default 90 days), IP allowlists are per key, and revocation lands on the very next request. Proxy calls are capped at 60 req/min per key.

Least privilege by default

Scopes like openai:chat or github:create-repo bind each token to named actions. Raw-value reads live behind their own scope, granted only to keys in your terminal.

Identity and sessions

Google sign-in, opaque server-side sessions, one-click logout. Tokens carry expiries and optional IP allowlists; revocation is a single call.

Audit everything

Every store, reveal, proxy call, and revocation records who acted, what ran, and from where — values never included. Read yours any time with audit:read.

Limits, stated plainly

KeyVeil removes the secret from the agent's world — it does not remove spend: a compromised agent token can still use your quota within its scopes until you revoke it. Scope stored keys minimally at the provider too, and treat Cloudflare (which operates the infra) as in the trust path. Full notes: docs/SECURITY.md.

Audit

Receipts, not promises.

Real rows from this site's production audit log — timestamp, actor, action, result. Values are never recorded. See your own live in the dashboard.

Production KeyVeil audit log rows
Live D1 rows · open your audit tab →

FAQ

Objections, answered.

Why not just use environment variables?

Because your agent can read them. One printenv in a tool call and the key is in the transcript, the logs, and possibly the model's training-adjacent data. KeyVeil keeps the key in the Worker and hands the agent a result — there is nothing in the environment worth stealing.

Why not Doppler, 1Password, or Vault?

Those are excellent at getting a secret to a process — which then holds it in memory, env, or a file your agent can also read. KeyVeil never hands the secret to the process at all: it injects the key server-side at call time and returns only the upstream result.

What if KeyVeil is down? Is it self-hostable?

Yes — loudly. It's one Worker plus D1 plus KV, no other infrastructure. Clone the repo and:

wrangler d1 create keyveil-db
wrangler kv:namespace create VEIL_KV
wrangler d1 execute keyveil-db --file=db/schema.sql --remote
wrangler secret put ENCRYPTION_KEK   # + GOOGLE_CLIENT_ID/SECRET, SESSION_SECRET
npm run deploy:api && npm run deploy:web

Full steps: docs/SETUP.md.

Do you see my keys? What's the encryption model?

Values arrive over TLS, are encrypted with AES-256-GCM under your per-account data key, and stored as ciphertext in KV. The master KEK lives only as a Cloudflare Worker secret. D1 keeps names, masked previews (sk-p…f4a2), and audit rows — never values, never in logs. Honest caveat: Cloudflare operates the infrastructure, so it sits in the trust path; what KeyVeil guarantees is that agents, transcripts, repos, and logs never see your keys.

What does it cost?

Free while in beta — hosted, no card. If you outgrow that or want full custody, self-host on Cloudflare's free tier in about ten minutes (above). Paid tiers only arrive with SLAs and team features.

Pricing

Free while in beta. Self-host anytime.

Hosted KeyVeil costs nothing right now. When you want custody, the whole gateway is a Worker, a D1, and a KV you can run yourself.

Put every key
to work

Continue with Google