@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
101 lines (100 loc) • 3.35 kB
JavaScript
;
/**
* 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;