The decrypt service for secret.agent.coop: it holds the private key that
decrypts your messages and decrypts them for your agent. This is the code that runs the hosted
default at decrypt.agent.coop, and it is what you deploy to run your own.
Status: stage 1 built (2026-09-12). getKey, rotateKey, getKeys, decryptEnvelope;
multi-tenant with keys in D1 wrapped under a KEK secret; interop vectors from jose and jwcrypto.
Single-tenant mode (keys in secrets, no D1) and call chaining are later stages.
- A Cloudflare Worker, an AAuth resource with
access_mode: person-token. Identity is the(iss, sub)pair from the person token, directed to this service. It never sees an email address; events carry a hash of the identity. - Operations (
/openapi.json):GET /keycurrent public key, created if none ·POST /keymint a new key, older keys stay decryptable ·GET /keysall keys ·POST /decryptdecrypt one message. - Keys: P-256, used as JWE
ECDH-ESwithA256GCM. One format, stored disassembled: spec/container.md. Decryption is Web Crypto directly (src/jwe.ts), no library, checked against the vectors inspec/vectors/. - Private keys at rest are AES-256-GCM wrapped under the
KEKsecret with(iss, sub, kid)as AAD. - Ciphertext up to 1 MiB. In the Workers test runtime a 1 MiB decrypt round trip (signature verification included) is about 60 ms wall; the Free-plan CPU figure on a real deployment is still to be read from Workers Logs.
With the AAuth MCP: connect_resource decrypt.agent.coop, then invoke getKey; register the result
at secret.agent.coop with addKey {kid, alg, jwk}. To read a message: getMessage at
secret.agent.coop (JSON form), then decryptEnvelope here with {protected, iv, tag, ciphertext}
where ciphertext is the blob field. The full flow is in the
secret-agent-coop skill.
npm install
npx wrangler d1 create decrypt-agent-coop # put the id in wrangler.jsonc
npx wrangler d1 migrations apply DB --remote
npm run generate-kek | npx wrangler secret put KEK
npm run generate-key | npx wrangler secret put SIGNING_KEY
npx wrangler deploy # set your own route / custom domain in wrangler.jsonc
Then connect_resource <your host> from your agent, getKey, and register that key at
secret.agent.coop. secret does not need to know where the private key lives (plan D16).
npm test # vitest, Workers pool, D1 in Miniflare, fake Person Server in test/fake-ps
npm run typecheck
npm run vectors # regenerate spec/vectors/jose.json
python3 scripts/vectors_jwcrypto.py # needs `pip install jwcrypto`
MIT