UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

101 lines (100 loc) 3.35 kB
"use strict"; /** * Cloudflare KV-based nonce cache implementation for Workers * * This cache prevents replay attacks by tracking used nonces in Cloudflare KV. * Each nonce is stored with a TTL matching the session timeout. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CloudflareKVNonceCache = void 0; /** * Cloudflare KV nonce cache for Workers * * Usage in Worker: * ```typescript * export interface Env { * NONCE_CACHE: KVNamespace; * } * * export default { * async fetch(request: Request, env: Env): Promise<Response> { * const nonceCache = new CloudflareKVNonceCache({ * namespace: env.NONCE_CACHE, * ttl: 1800, // 30 minutes * }); * * // Use with verifier or MCP-I runtime * const runtime = new MCPIRuntime({ * nonce: { cache: nonceCache } * }); * } * } * ``` */ class CloudflareKVNonceCache { kv; ttl; keyPrefix; constructor(config) { this.kv = config.namespace; this.ttl = config.ttl ?? 1800; // 30 minutes default this.keyPrefix = config.keyPrefix ?? "nonce:"; } /** * Check if a nonce exists in the cache * * @param nonce - The nonce to check * @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks) * @returns Promise<boolean> - true if exists, false if not */ async has(nonce, agentDid) { const key = agentDid ? `${this.keyPrefix}${agentDid}:${nonce}` : this.keyPrefix + nonce; try { const existing = await this.kv.get(key, { type: "text" }); return existing !== null; } catch (error) { console.error("CloudflareKVNonceCache.has error:", error); // On error, assume nonce exists (fail closed for security) return true; } } /** * Add a nonce to the cache with TTL * Implements atomic add-if-absent semantics for replay prevention * * @param nonce - The nonce to add * @param ttl - Time-to-live in seconds * @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks) */ async add(nonce, ttl, agentDid) { const key = agentDid ? `${this.keyPrefix}${agentDid}:${nonce}` : this.keyPrefix + nonce; try { // Check if nonce already exists const existing = await this.kv.get(key, { type: "text" }); if (existing !== null) { throw new Error(`Nonce ${nonce} already exists (replay attack detected)`); } // Add nonce with TTL await this.kv.put(key, new Date().toISOString(), { expirationTtl: ttl, }); } catch (error) { if (error instanceof Error && error.message.includes("already exists")) { throw error; } console.error("CloudflareKVNonceCache.add error:", error); throw new Error("Failed to add nonce to cache"); } } /** * Cleanup expired nonces * Note: Cloudflare KV handles expiration automatically via TTL */ async cleanup() { // KV automatically expires entries based on TTL // No manual cleanup needed } } exports.CloudflareKVNonceCache = CloudflareKVNonceCache;