Deno Deploy · AI agents · API key management · serverless
Deno Deploy AI agent API key management: vault keys for serverless agent functions
Deno Deploy runs JavaScript and TypeScript in V8 isolates with strong security defaults — no file system access, explicit network allowlists, and environment variables stored as encrypted secrets. It's a natural fit for AI agent tool functions that call vendor APIs. The security gap is at the invocation boundary: all concurrent Deno Deploy invocations share the same Deno.env.get("STRIPE_KEY") secret. A function handling a $10 payment and one handling a $10,000 payment use the same credential, with no per-invocation spend cap or endpoint scope. Vault keys close this gap by providing per-invocation credentials that carry spend limits and allowed-endpoint policies — without changing how Deno Deploy stores or accesses the underlying Keybrake API token.
TL;DR
Store the Keybrake API token (not the Stripe secret) in Deno Deploy's environment secrets. At the start of each agent invocation, call the Keybrake API to issue a vault key scoped to the specific vendor, spend cap, and allowed endpoints for that invocation. Use the vault key token as the Authorization: Bearer header when calling https://proxy.keybrake.com/stripe/.... The vault key auto-expires after its TTL; the real Stripe secret never leaves Keybrake's proxy infrastructure and never appears in Deno Deploy's fetch requests.
Deno Deploy's security model for API keys
Deno Deploy provides two mechanisms for storing API credentials:
- Environment variables — set via the Deno Deploy dashboard or
deployctlCLI; available at runtime viaDeno.env.get("KEY_NAME"). These are encrypted at rest by Deno Deploy and not exposed in logs or deployment artifacts. - Request-level injection — for preview deployments, env vars can be scoped to specific deployment environments (production vs. preview). This provides deployment-level isolation but not invocation-level isolation.
Both mechanisms are good for preventing credential leakage through source code or deployment artifacts. Neither provides:
- Per-invocation spend caps (limit how much a single function execution can charge)
- Per-invocation endpoint allowlists (restrict which Stripe endpoints this execution can hit)
- Independent revocation (stop one misbehaving invocation without affecting others)
- Per-invocation attribution in the vendor audit log (link a Stripe charge to a specific Deno Deploy invocation ID)
The vault key pattern for Deno Deploy
The vault key pattern for Deno Deploy keeps the Keybrake API token in Deno.env and uses it to issue short-lived, scoped vault keys per invocation:
// Deno Deploy agent function: charge a customer via vault-key-scoped proxy
export default async function handler(req: Request): Promise {
const body = await req.json();
const { customerId, amountCents } = body;
// Step 1: Issue a vault key scoped to this invocation
// The KEYBRAKE_TOKEN in Deno.env is the Keybrake API token — not the Stripe secret
const keyRes = await fetch("https://api.keybrake.com/v1/keys", {
method: "POST",
headers: {
"Authorization": `Bearer ${Deno.env.get("KEYBRAKE_TOKEN")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
label: `deno-charge-${req.headers.get("x-request-id") ?? crypto.randomUUID()}`,
vendor: "stripe",
allowed_endpoints: ["/v1/payment_intents", "/v1/payment_intents/*"],
daily_usd_cap: 500, // This invocation can charge at most $500
expires_in: "5m" // Auto-expire after 5 minutes
}),
});
if (!keyRes.ok) {
return new Response(JSON.stringify({ error: "key_issuance_failed" }), { status: 500 });
}
const { token: vaultKey, id: keyId } = await keyRes.json();
try {
// Step 2: Call Stripe via Keybrake proxy using the vault key
const stripeRes = await fetch("https://proxy.keybrake.com/stripe/v1/payment_intents", {
method: "POST",
headers: {
"Authorization": `Bearer ${vaultKey}`, // vault key, not Stripe secret
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: amountCents,
currency: "usd",
customer: customerId,
confirm: true,
automatic_payment_methods: { enabled: true },
}),
});
if (stripeRes.status === 429) {
const err = await stripeRes.json();
if (err.code === "cap_exhausted") {
return new Response(JSON.stringify({ error: "spend_cap_exceeded" }), { status: 402 });
}
}
const payment = await stripeRes.json();
return new Response(JSON.stringify(payment), { status: stripeRes.status });
} finally {
// Step 3: Revoke vault key immediately after use — don't wait for TTL
await fetch(`https://api.keybrake.com/v1/keys/${keyId}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${Deno.env.get("KEYBRAKE_TOKEN")}` },
});
}
}
What lives in Deno.env vs. what travels in requests
| Credential | Stored in | Used for | Visible to agent code? |
|---|---|---|---|
| Stripe secret key | Keybrake's encrypted secrets vault | Forwarding proxied requests to Stripe | No — never leaves Keybrake infrastructure |
| Keybrake API token | Deno.env.get("KEYBRAKE_TOKEN") |
Issuing and revoking vault keys | Yes — present in Deno.env; used to issue vault keys |
| Vault key token | Local variable in function scope | Authorizing proxied vendor calls | Yes — scoped to the invocation; expires in minutes |
The security property is that the Keybrake API token (the secret that can issue unlimited vault keys) is what lives in Deno.env — analogous to how a password manager's master password lives in secure storage while individual site passwords are retrieved on demand. Compromising a single vault key (e.g., through a log injection) exposes a credential that is already expiring and scoped to a small number of endpoints with a low spend cap — not the full Stripe secret.
Deno Deploy's network permissions and the proxy URL
Deno Deploy's production environment allows outbound fetch() to any HTTPS URL by default. You do not need to configure a special allowlist entry for proxy.keybrake.com or api.keybrake.com. Deno's permission system (--allow-net) applies to the Deno CLI; Deno Deploy's serverless environment has fetch available without explicit permissions. If you are testing locally with the Deno CLI, add --allow-net=api.keybrake.com,proxy.keybrake.com to your invocation.
Multi-tenant Deno Deploy agents
For multi-tenant deployments where different users have different Stripe accounts, the vault key pattern extends naturally. Store the Keybrake vendor credential (a separate Keybrake API token or a tenant-scoped credential) per-tenant in a database or KV store, not in Deno.env. At invocation time, look up the tenant's Keybrake token, issue a vault key for that tenant's vendor, and proxy calls with the per-tenant vault key:
// Multi-tenant: each tenant's Stripe account is registered separately in Keybrake
// Look up tenant's Keybrake token from Deno KV
const kv = await Deno.openKv();
const tenantToken = await kv.get(["tenants", tenantId, "keybrake_token"]);
const vaultKey = await issueVaultKey(tenantToken.value, {
vendor: "stripe",
allowed_endpoints: ["/v1/charges", "/v1/charges/*"],
daily_usd_cap: tenantDailyCap,
expires_in: "10m",
label: `tenant-${tenantId}-run-${runId}`
});
This pattern keeps each tenant's Stripe access isolated at the vault key level — a vault key issued for tenant A's Stripe account cannot be used against tenant B's — while keeping all the enforcement (spend caps, endpoint allowlists, audit logs) in Keybrake rather than in your Deno Deploy function logic.
Related questions
Does Deno Deploy support fetch() to proxy.keybrake.com without special configuration?
Yes. Deno Deploy's serverless environment allows outbound HTTPS fetch to any URL without configuration. The --allow-net permission flag only applies when running Deno locally via the CLI. In Deno Deploy, your function can call fetch("https://proxy.keybrake.com/stripe/...") directly. If you're testing locally before deploying, run with deno run --allow-net=api.keybrake.com,proxy.keybrake.com your-function.ts to grant network access to exactly the Keybrake hosts your function needs.
How does this interact with Deno's Fresh framework?
Fresh is Deno's web framework built on Deno Deploy. Fresh API route handlers and server-side components run as Deno Deploy functions — the vault key pattern applies identically. Issue a vault key in your Fresh API route handler at the start of the request, pass it to any vendor API calls made in that handler, and revoke it in a try/finally block. Fresh's island architecture means browser-side JavaScript does not have access to server-side credentials; all vendor API calls happen in the server handler where the vault key is scoped to the request.
What's the overhead of issuing a vault key per invocation?
Issuing a vault key requires one HTTPS API call to api.keybrake.com before the first vendor call. In Deno Deploy's edge network, this round-trip is typically 20–50ms from any region. For functions where vendor API calls are the majority of execution time (e.g., Stripe payment processing at 200–500ms), the vault key issuance overhead is 5–15% of total duration — acceptable for the security and governance benefit. For extremely latency-sensitive paths (under 100ms total budget), you can issue vault keys with longer TTLs (10–30 minutes) outside the request handler and cache them in Deno KV, treating them as short-lived session tokens rather than per-request credentials.
How does this work with Deno Queues for agent task processing?
Deno Queues (built on Deno KV) deliver messages to handlers asynchronously. A queue handler processes one message at a time per isolate. Issue a vault key at the start of the queue handler (when the message is dequeued), use it for all vendor calls made while processing that message, and revoke it when the handler completes. The vault key's label should include the queue message ID or a task ID from the message body — this lets you correlate Keybrake audit log entries with specific queue messages for debugging and compliance.
Further reading
- AI agent API key lifecycle — the four phases (issuance, enforcement, expiration, revocation) that give vault keys a defined lifecycle unlike shared long-lived API keys.
- Cloudflare Workers AI agent API key — the same vault key pattern applied to Cloudflare Workers' edge runtime and Workers AI agent pipelines.
- FastAPI AI agent API key — vault key patterns for Python-based AI agent backends using FastAPI, analogous to the Deno Deploy serverless pattern.
- Modal AI agent API key — vault key integration with Modal's serverless Python runtime, which has similar per-invocation isolation properties to Deno Deploy.
- AI agent secrets management — where and how to store vendor API keys for the proxy layer that holds them on behalf of Deno Deploy agent functions.