Scoped, revocable, metered secret access for AI agents β where the agent uses your secret without ever seeing it.
Live app: keyring-ormp.onrender.com Β· Demo server: keyring-neon.vercel.app
AI agents need your secrets to be useful β your API keys, database passwords, exchange keys. Today you hand them the raw secret as a plaintext environment variable, and from that moment you've lost control: the agent has the key forever, can leak it, and you can't take it back without rotating everything.
Keyring is a vault that lets an agent use a secret without ever receiving it. You store a secret once. You give an agent a plain-English policy ("you may only read from this one API"). When the agent makes a request, Keyring decrypts the secret inside a sealed hardware enclave, an AI judge checks the request against your policy, the secret is injected into the outgoing call, and the agent gets back only the result β never the key itself.
- π§βπ» No blockchain knowledge required. Sign in with Google, email, or an external wallet. Setup takes a few one-click signatures β no seed phrases, no tokens, nothing else to learn.
- π Security is the whole point. The secret is decrypted only inside an attested Trusted Execution Environment, used once, and wiped. The agent and its host never see it.
- β¨ Built to be easy. Create a secret, create an agent, copy an API key. That's it.
- β½ Zero gas, ever. Every blockchain transaction is sponsored by the protocol. You never pay or hold a single token.
| The problem today | Keyring's answer |
|---|---|
| You give an agent the raw secret β permanent exposure. | The agent gets a result, never the secret. The key is decrypted only inside a sealed enclave. |
| No way to revoke without rotating the secret everywhere. | One on-chain transaction revokes an agent's access instantly, globally. |
| No scoping β a key is all-or-nothing. | A natural-language policy + an AI judge gate every single request to exactly what you allow. |
| No metering β you can't cap how much an agent uses. | Per-agent rate limits: all-time, per-minute, per-hour, per-day. |
| Secret managers are centralized β trust the host. | Storage is threshold-encrypted on-chain (CDR); access is gated by on-chain contracts anyone can audit. |
Keyring combines three pieces: Story Protocol CDR for encrypted storage, on-chain contracts for access control, and a Phala TDX enclave for the moment of use.
flowchart LR
AG["π€ Agent<br/>POST /api/<key>"] --> API
subgraph KEYRING["Keyring API (Next.js)"]
API["Validate key, agent,<br/>policy, rate limits,<br/>allowed secrets"]
end
subgraph CHAIN["π’ Story Protocol β Aeneid"]
COND["KeyringAccessCondition<br/>checkReadCondition()"]
CDR[("CDR Vault<br/>threshold-encrypted")]
end
subgraph TEE["π΄ Phala TDX Enclave"]
JUDGE["Gemini Policy Judge<br/>(sees placeholders only)"]
EXEC["Inject real secret β<br/>execute HTTP call"]
end
API -->|"unlock (gasless UserOp)"| COND
COND -->|"grant valid?"| CDR
CDR -->|"plaintext secret"| TEE
API -->|"policy + operation + task"| JUDGE
JUDGE -->|"deny β 403"| AG
JUDGE -->|"allow"| EXEC
EXEC -->|"result only + TDX attestation"| AG
How CDR is used. Every secret is stored as a Confidential Data Rails (CDR) vault β threshold-encrypted (TDH2) on Story Protocol. The encrypted data lives on-chain; the key is never in one place. Decryption is gated by our on-chain condition contract (KeyringAccessCondition.checkReadCondition) β CDR's validator network only releases partial decryptions when that contract returns true, which it does only for an agent holding a valid on-chain grant. The plaintext is recombined server-side, handed to the enclave, used once, and discarded.
How Story Protocol is used. Keyring runs on the Aeneid testnet. On first login, a per-user pair of contracts (AgentRegistry + KeyringAccessCondition) is deployed for you via KeyringFactory. Each agent you create is registered as a Story Protocol IP Asset (AgentRegistry.createAgent, via RegistrationWorkflows). Granting and revoking a secret are on-chain calls (grantAccess / revokeAccess). Every transaction is a gasless ERC-4337 UserOperation sponsored by a Pimlico paymaster β sent from a Privy smart wallet, so you never pay gas or manage a key.
The moment of use (the TEE). When an agent calls, the decrypted secret enters a Phala TDX enclave. The agent's request is shown to a Gemini policy judge as {{PLACEHOLDERS}} only β the AI never sees real secret values. The judge rules on your policy. If denied, nothing executes. If allowed, the real secret is injected into the outgoing request inside the enclave, the call is made, and only the response returns β bound to a hardware TDX attestation proving which code ran.
This is the core guarantee: a secret, even at the instant it's decrypted, is never revealed to anyone. It's used inside hardware-isolated enclave memory that not even the machine's operator can read, then wiped β never handed back. The agent that asked for it receives only the result of the operation, so even a fully compromised agent or a malicious host has nothing to steal.
You'll do this in two parts: (A) set up your vault and agent in the dashboard, then (B) test the full flow against our demo server, acting as the agent yourself.
Go to keyring-ormp.onrender.com and sign in however you prefer β Google, email, or an external wallet (via Privy). A smart wallet is created for you automatically β no extension or seed phrase needed.
The first time you open the dashboard, Keyring deploys your personal AgentRegistry + KeyringAccessCondition contracts on Aeneid. Sign the prompt when it appears, then wait a few seconds for "On-chain contracts ready." Signing is free β Keyring sponsors the gas.
Go to Secrets β Add secret. Enter a name (e.g. SECRETVAULTKEY) and its value. On save, Keyring:
- Encrypts it into a CDR vault (threshold encryption + on-chain write),
- Registers vault ownership (
registerVault), - Saves the reference.
Sign the prompts when they appear and wait for confirmation (gasless, sponsored by Keyring). The plaintext value is never stored in our database; only the encrypted CDR vault holds it.
Go to Agents β New agent.
-
Step 1 β Configure: name the agent and tick which secrets it may access.
-
Step 2 β Policy & limits: write a plain-English policy describing exactly what the agent may and may not do (this is what the AI judge enforces). Optionally tick Enable rate limit to cap calls (all-time total, and/or per minute / hour / day).
Example policy: "This agent may only make GET requests to the demo vault API to retrieve a secret for testing. No POST, PUT, DELETE or write operations are permitted."
On create, the agent is registered on-chain as a Story Protocol IP Asset and granted access to each selected secret. Sign the prompts and wait for confirmation β gasless, as always.
- From the agent's row, copy the Agent ID (its on-chain credential).
- Go to the API tab and copy your API key (
kr_β¦).
You now have everything to make a call: an API key, an Agent ID, and a secret name.
To test without building a real agent, you play the role of the agent and call Keyring with a raw curl. We provide a demo server to act as the "external API" your secret unlocks.
The demo server β keyring-neon.vercel.app β is a tiny "magic phrase vault." On its homepage you create a magic phrase β secret message mapping. Then:
GET https://keyring-neon.vercel.app/api/<magic-phrase> β returns the secret message
GET https://keyring-neon.vercel.app/api/<wrong-phrase> β "error: unknown magic phrase"
Try it directly in your browser first to see it work.
The idea: store the magic phrase as a secret in Keyring. The agent never learns the phrase β Keyring injects it into the demo-server URL inside the enclave and returns only the secret message.
- On the demo site, create a mapping, e.g. phrase
open-sesameβ secret"the eagle flies at midnight". - In Keyring, create a secret named
SECRETVAULTKEYwith the valueopen-sesame(the phrase). - Create an agent with access to
SECRETVAULTKEYand the policy above.
curl -X POST https://keyring-ormp.onrender.com/api/<YOUR_API_KEY> \
-H "Content-Type: application/json" \
-d '{
"agentId": "<YOUR_AGENT_ID>",
"secretsRequested": ["SECRETVAULTKEY"],
"operationRequested": "retrieve the secret message from the demo vault",
"task": {
"url": "https://keyring-neon.vercel.app/api/{{SECRETVAULTKEY}}",
"method": "GET"
}
}'What happens: Keyring decrypts SECRETVAULTKEY (β open-sesame) from its CDR vault, the Gemini judge confirms a GET read matches your policy, the enclave replaces {{SECRETVAULTKEY}} with open-sesame, calls the demo server, and returns the secret message β you never saw the phrase.
POST https://keyring-ormp.onrender.com/api/<API_KEY> Β· Content-Type: application/json
{
"agentId": "0xβ¦",
"secretsRequested": ["SECRETVAULTKEY"],
"operationRequested": "retrieve the secret message from the demo vault",
"task": {
"url": "https://keyring-neon.vercel.app/api/{{SECRETVAULTKEY}}",
"method": "GET",
"headers": { "Authorization": "Bearer {{SECRETVAULTKEY}}" },
"body": null
}
}| Field | Required | Description |
|---|---|---|
agentId |
β | The agent's on-chain credential, copied from its dashboard row. Identifies which agent (and which policy + grants) this request runs under. |
secretsRequested |
β | Array of secret names to unlock. Each must be in this agent's allowed list, or the call is rejected 403. |
operationRequested |
β | Plain-English description of what you're doing. This is what the AI judge evaluates against the agent's policy. |
task.url |
β | The outgoing request URL. Use {{SECRET_NAME}} placeholders β they're replaced with real values only inside the enclave. |
task.method |
β | HTTP method for the outgoing call (GET, POST, β¦). |
task.headers |
β | Optional headers for the outgoing call. May contain {{PLACEHOLDERS}} (e.g. a bearer token). |
task.body |
β | Optional request body. May contain {{PLACEHOLDERS}}. |
π Placeholders are the safety mechanism. Anywhere you'd put a secret, write
{{SECRET_NAME}}. The AI judge sees only the placeholder; the real value is injected after approval, inside the TEE.
{
"ok": true,
"allowed": true,
"reason": "GET read matches the policy's permitted operations.",
"taskStatus": 200,
"taskResponse": "the eagle flies at midnight",
"attestation": { "...": "TDX attestation quote" }
}| Field | Description |
|---|---|
ok / allowed |
Both true when the policy passed and the call executed. |
reason |
The judge's plain-English justification for its verdict. |
taskStatus |
HTTP status returned by the upstream call (the demo server here). |
taskResponse |
The upstream response body β this is the result your agent wanted. |
attestation |
Phala TDX attestation quote proving the operation ran in the genuine enclave. |
| Status | Body | Meaning |
|---|---|---|
403 |
{ "ok": false, "allowed": false, "reason": "β¦" } |
The AI judge denied the request β it violated the agent's policy. Nothing executed. |
403 |
{ "error": "Secret not allowed for this agent: β¦" } |
The agent isn't granted one of the requested secrets. |
401 |
{ "error": "Invalid API key" } / { "error": "Agent not recognised" } |
Bad API key or agent ID. |
429 |
{ "error": "Rate limit exceeded" } |
A user- or agent-level rate limit was hit. |
502 |
{ "ok": false, "error": "β¦" } |
Vault unlock, the policy judge, or the upstream call failed (fail-closed). |
| Layer | Technology |
|---|---|
| Blockchain | Story Protocol β Aeneid testnet |
| Confidential storage | CDR (TDH2 threshold encryption) via @piplabs/cdr-sdk |
| Contracts | KeyringFactory, AgentRegistry, KeyringAccessCondition (Solidity / Foundry) |
| Gasless txns | ERC-4337 UserOps β Privy smart wallets + Pimlico paymaster |
| TEE | Phala dstack / Intel TDX (@phala/dstack-sdk) |
| Policy judge | Google Gemini (structured output, fail-closed) |
| Auth | Privy (social login + smart wallets) |
| App | Next.js (App Router), Drizzle + Neon Postgres |
client/ Next.js dashboard + agent API (the product)
tee-worker/ Phala TDX enclave: Gemini judge + secret injection + execution
contracts/ Solidity contracts (Foundry)
demo/ Magic-phrase vault β stand-in "external API" for testing
make install # install client + tee-worker deps
make dev # start dstack simulator, tee-worker, and the dashboardCopy .env.example to .env and fill in the values (Privy, Neon, Pimlico, Gemini, factory address). See the Makefile for all targets.