UNPKG

@kya-os/agentshield-nextjs

Version:

Next.js middleware for AgentShield AI agent detection

1,051 lines (1,041 loc) 38.7 kB
import { loadRulesSync, mapVerificationMethod } from '@kya-os/agentshield-shared'; import { NextResponse } from 'next/server'; var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/wasm-confidence.ts var wasm_confidence_exports = {}; __export(wasm_confidence_exports, { checkWasmAvailability: () => checkWasmAvailability, getVerificationMethod: () => getVerificationMethod, getWasmConfidenceBoost: () => getWasmConfidenceBoost, shouldIndicateWasmVerification: () => shouldIndicateWasmVerification }); async function checkWasmAvailability() { try { if (typeof WebAssembly === "undefined") { return false; } if (!WebAssembly.instantiate || !WebAssembly.Module) { return false; } return true; } catch { return false; } } function shouldIndicateWasmVerification(confidence) { return confidence >= 85 && confidence < 100; } function getWasmConfidenceBoost(baseConfidence, reasons = []) { if (reasons.some( (r) => r.includes("signature_agent") && !r.includes("signature_headers_present") )) { return 100; } if (baseConfidence >= 85) { return 95; } if (baseConfidence >= 70) { return Math.min(baseConfidence * 1.1, 90); } return baseConfidence; } function getVerificationMethod(confidence, reasons = []) { if (reasons.some( (r) => r.includes("signature_agent") && !r.includes("signature_headers_present") )) { return "signature"; } if (shouldIndicateWasmVerification(confidence)) { return "wasm-enhanced"; } return "pattern"; } var init_wasm_confidence = __esm({ "src/wasm-confidence.ts"() { } }); // src/wasm-loader.ts function setWasmBaseUrl(url) { baseUrl = url; } function getWasmUrl() { if (baseUrl) { try { const url = new URL(baseUrl); return `${url.origin}${WASM_PATH}`; } catch { return WASM_PATH; } } return WASM_PATH; } async function initWasm() { if (wasmExports) return true; if (initPromise) { await initPromise; return !!wasmExports; } initPromise = (async () => { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 3e3); try { const wasmUrl = getWasmUrl(); if (typeof WebAssembly.instantiateStreaming === "function") { try { const response2 = await fetch(wasmUrl, { signal: controller.signal }); clearTimeout(timeout); if (!response2.ok) { throw new Error(`Failed to fetch WASM: ${response2.status}`); } const streamResponse = response2.clone(); const { instance } = await WebAssembly.instantiateStreaming(streamResponse, { wbg: { __wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => { if (process.env.NODE_ENV !== "production") { console.debug("WASM:", ptr, len); } }, __wbindgen_throw: (ptr, len) => { throw new Error(`WASM Error at ${ptr}, length ${len}`); } } }); wasmInstance = instance; wasmExports = instance.exports; if (process.env.NODE_ENV !== "production") { console.debug("[AgentShield] \u2705 WASM module initialized with streaming"); } return; } catch (streamError) { if (!controller.signal.aborted) { if (process.env.NODE_ENV !== "production") { console.debug( "[AgentShield] Streaming compilation failed, falling back to standard compilation" ); } } else { throw streamError; } } } const response = await fetch(wasmUrl, { signal: controller.signal }); clearTimeout(timeout); if (!response.ok) { throw new Error(`Failed to fetch WASM: ${response.status}`); } const wasmArrayBuffer = await response.arrayBuffer(); const compiledModule = await WebAssembly.compile(wasmArrayBuffer); const imports = { wbg: { __wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => { if (process.env.NODE_ENV !== "production") { console.debug("WASM:", ptr, len); } }, __wbindgen_throw: (ptr, len) => { throw new Error(`WASM Error at ${ptr}, length ${len}`); } } }; wasmInstance = await WebAssembly.instantiate(compiledModule, imports); wasmExports = wasmInstance.exports; if (process.env.NODE_ENV !== "production") { console.debug("[AgentShield] \u2705 WASM module initialized via fallback"); } } catch (fetchError) { const error = fetchError; if (error.name === "AbortError") { console.warn( "[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection" ); } else { console.warn( "[AgentShield] Failed to fetch WASM file:", error.message || "Unknown error" ); } wasmExports = null; } } catch (error) { console.error("[AgentShield] Failed to initialize WASM:", error); wasmExports = null; } })(); await initPromise; return !!wasmExports; } async function detectAgentWithWasm(_userAgent, _headers, _ipAddress) { return null; } async function getWasmVersion() { const initialized = await initWasm(); if (!initialized || !wasmExports) { return null; } if (typeof wasmExports.version === "function") { return wasmExports.version(); } return "unknown"; } async function isWasmAvailable() { try { const initialized = await initWasm(); if (!initialized) return false; const version = await getWasmVersion(); return version !== null; } catch { return false; } } var wasmInstance, wasmExports, initPromise, WASM_PATH, baseUrl; var init_wasm_loader = __esm({ "src/wasm-loader.ts"() { wasmInstance = null; wasmExports = null; initPromise = null; WASM_PATH = "/wasm/agentshield_wasm_bg.wasm"; baseUrl = null; } }); // src/edge-detector-with-wasm.ts var edge_detector_with_wasm_exports = {}; __export(edge_detector_with_wasm_exports, { EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm }); var rules, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm; var init_edge_detector_with_wasm = __esm({ "src/edge-detector-with-wasm.ts"() { init_wasm_loader(); rules = loadRulesSync(); EdgeAgentDetectorWithWasm = class { constructor(enableWasm = true) { this.enableWasm = enableWasm; this.rules = rules; } wasmEnabled = false; initPromise = null; baseUrl = null; rules; /** * Set the base URL for WASM loading in Edge Runtime */ setBaseUrl(url) { this.baseUrl = url; setWasmBaseUrl(url); } /** * Initialize the detector (including WASM if enabled) */ async init() { if (!this.enableWasm) { this.wasmEnabled = false; return; } if (this.initPromise) { await this.initPromise; return; } this.initPromise = (async () => { try { const wasmAvailable = await isWasmAvailable(); if (wasmAvailable) { if (this.baseUrl) { setWasmBaseUrl(this.baseUrl); } await initWasm(); this.wasmEnabled = true; } else { this.wasmEnabled = false; } } catch (error) { console.error("[AgentShield] Failed to initialize WASM:", error); this.wasmEnabled = false; } })(); await this.initPromise; } /** * Pattern-based detection (fallback) */ async patternDetection(input) { const reasons = []; let detectedAgent; let verificationMethod; let confidence = 0; const headers = input.headers || {}; const normalizedHeaders = {}; for (const [key, value] of Object.entries(headers)) { normalizedHeaders[key.toLowerCase()] = value; } const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]); const signatureAgent = normalizedHeaders["signature-agent"]; if (signatureAgent?.includes("chatgpt.com")) { confidence = 85; reasons.push("signature_agent:chatgpt"); detectedAgent = { type: "chatgpt", name: "ChatGPT" }; verificationMethod = "signature"; } else if (signaturePresent) { confidence = Math.max(confidence, 40); reasons.push("signature_present"); } const userAgent = input.userAgent || input.headers?.["user-agent"] || ""; if (userAgent) { for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) { const matched = agentRule.patterns.some((pattern) => { const regex = new RegExp(pattern, "i"); return regex.test(userAgent); }); if (matched) { const agentType = this.getAgentType(agentKey); const agentName = this.getAgentName(agentKey); confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100)); reasons.push(`known_pattern:${agentType}`); if (!detectedAgent) { detectedAgent = { type: agentType, name: agentName }; verificationMethod = "pattern"; } break; } } } const suspiciousHeaders = this.rules.rules.headers.suspicious; const foundAiHeaders = suspiciousHeaders.filter( (headerRule) => normalizedHeaders[headerRule.name.toLowerCase()] ); if (foundAiHeaders.length > 0) { const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence)); confidence = Math.max(confidence, maxConfidence); reasons.push(`ai_headers:${foundAiHeaders.length}`); } const ip = input.ip || input.ipAddress; if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) { const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges; for (const [provider, ipRule] of Object.entries(ipRanges)) { if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges)) continue; const matched = ipRule.ranges.some((range) => { const prefix = range.split("/")[0]; const prefixParts = prefix.split("."); const ipParts = ip.split("."); for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) { if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") { return false; } } return true; }); if (matched) { confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100)); reasons.push(`cloud_provider:${provider}`); break; } } } if (reasons.length > 2) { confidence = Math.min(Math.round(confidence * 1.2), 95); } confidence = Math.min(Math.max(confidence, 0), 100); return { isAgent: confidence > 30, // 30% threshold confidence, detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" }, signals: [], // Will be populated by enhanced detection engine in future tasks ...detectedAgent && { detectedAgent }, reasons, ...verificationMethod && { verificationMethod: mapVerificationMethod(verificationMethod) }, forgeabilityRisk: confidence > 80 ? "medium" : "high", timestamp: /* @__PURE__ */ new Date() }; } /** * Analyze request with WASM enhancement when available */ async analyze(input) { await this.init(); if (this.wasmEnabled) { try { const wasmResult = await detectAgentWithWasm( input.userAgent || input.headers?.["user-agent"], input.headers || {}, input.ip || input.ipAddress ); if (wasmResult) { const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0; return { isAgent: wasmResult.isAgent, confidence: wasmResult.confidence, detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" }, signals: [], // Will be populated by enhanced detection engine in future tasks ...detectedAgent && { detectedAgent }, reasons: [`wasm:${wasmResult.verificationMethod}`], verificationMethod: mapVerificationMethod(wasmResult.verificationMethod), forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high", timestamp: /* @__PURE__ */ new Date() }; } } catch (error) { console.error("[AgentShield] WASM detection error:", error); } } const patternResult = await this.patternDetection(input); if (this.wasmEnabled && patternResult.confidence >= 85) { patternResult.confidence = Math.min(95, patternResult.confidence + 10); patternResult.reasons.push("wasm_enhanced"); } return patternResult; } /** * Get agent type from rule key */ getAgentType(agentKey) { const typeMap = { openai_gptbot: "openai", anthropic_claude: "anthropic", perplexity_bot: "perplexity", google_ai: "google", microsoft_ai: "microsoft", meta_ai: "meta", cohere_bot: "cohere", huggingface_bot: "huggingface", generic_bot: "generic", dev_tools: "dev", automation_tools: "automation" }; return typeMap[agentKey] || agentKey; } /** * Get agent name from rule key */ getAgentName(agentKey) { const nameMap = { openai_gptbot: "ChatGPT/GPTBot", anthropic_claude: "Claude", perplexity_bot: "Perplexity", google_ai: "Google AI", microsoft_ai: "Microsoft Copilot", meta_ai: "Meta AI", cohere_bot: "Cohere", huggingface_bot: "HuggingFace", generic_bot: "Generic Bot", dev_tools: "Development Tool", automation_tools: "Automation Tool" }; return nameMap[agentKey] || agentKey; } /** * Map agent names from WASM to consistent format */ mapAgentName(agent) { const lowerAgent = agent.toLowerCase(); if (lowerAgent.includes("chatgpt")) { return { type: "chatgpt", name: "ChatGPT" }; } else if (lowerAgent.includes("claude")) { return { type: "claude", name: "Claude" }; } else if (lowerAgent.includes("perplexity")) { return { type: "perplexity", name: "Perplexity" }; } else if (lowerAgent.includes("bing")) { return { type: "bing", name: "Bing AI" }; } else if (lowerAgent.includes("anthropic")) { return { type: "anthropic", name: "Anthropic" }; } return { type: "unknown", name: agent }; } }; EdgeAgentDetectorWrapperWithWasm = class { detector; events = /* @__PURE__ */ new Map(); constructor(config) { this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true); if (config?.baseUrl) { this.detector.setBaseUrl(config.baseUrl); } } setBaseUrl(url) { this.detector.setBaseUrl(url); } async analyze(input) { const result = await this.detector.analyze(input); if (result.isAgent && this.events.has("agent.detected")) { const handlers = this.events.get("agent.detected") || []; handlers.forEach((handler) => handler(result, input)); } return result; } on(event, handler) { if (!this.events.has(event)) { this.events.set(event, []); } this.events.get(event).push(handler); } emit(event, ...args) { const handlers = this.events.get(event) || []; handlers.forEach((handler) => handler(...args)); } async init() { await this.detector.init(); } }; } }); // src/edge-safe-detector.ts var AI_AGENT_PATTERNS = [ { pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" }, { pattern: /claude-web/i, type: "claude", name: "Claude" }, { pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" }, { pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" }, { pattern: /perplexity/i, type: "perplexity", name: "Perplexity" }, { pattern: /bingbot/i, type: "bing", name: "Bing AI" }, { pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" } ]; var EdgeSafeDetector = class { async analyze(input) { const reasons = []; let detectedAgent; let confidence = 0; const headers = input.headers || {}; const normalizedHeaders = {}; for (const [key, value] of Object.entries(headers)) { normalizedHeaders[key.toLowerCase()] = value; } const userAgent = input.userAgent || normalizedHeaders["user-agent"] || ""; if (userAgent) { for (const { pattern, type, name } of AI_AGENT_PATTERNS) { if (pattern.test(userAgent)) { confidence = 85; reasons.push(`known_pattern:${type}`); detectedAgent = { type, name }; break; } } } const hasChrome = userAgent.toLowerCase().includes("chrome"); const hasFirefox = userAgent.toLowerCase().includes("firefox"); const hasSafari = userAgent.toLowerCase().includes("safari"); const hasBrowserUA = hasChrome || hasFirefox || hasSafari; if (hasBrowserUA) { const hasSecChUa = !!normalizedHeaders["sec-ch-ua"]; const hasSecFetch = !!normalizedHeaders["sec-fetch-site"]; const hasAcceptLanguage = !!normalizedHeaders["accept-language"]; const hasCookies = !!normalizedHeaders["cookie"]; const missingHeaders = []; if (!hasSecChUa && hasChrome) missingHeaders.push("sec-ch-ua"); if (!hasSecFetch) missingHeaders.push("sec-fetch"); if (!hasAcceptLanguage) missingHeaders.push("accept-language"); if (!hasCookies) missingHeaders.push("cookies"); if (missingHeaders.length >= 2) { confidence = Math.max(confidence, 85); reasons.push("browser_ua_missing_headers"); if (!detectedAgent && hasChrome && !hasSecChUa) { detectedAgent = { type: "perplexity", name: "Perplexity" }; } } } const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"]; const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]); if (foundAiHeaders.length > 0) { confidence = Math.max(confidence, 60); reasons.push(`ai_headers:${foundAiHeaders.length}`); } return { isAgent: confidence > 30, // Updated to 0-100 scale (was 0.3) confidence, detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" }, signals: [], // Will be populated by enhanced detection engine in future tasks detectedAgent, reasons, verificationMethod: "pattern", timestamp: /* @__PURE__ */ new Date(), confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low" // Updated to 0-100 scale }; } }; // src/storage/memory-adapter.ts var MemoryStorageAdapter = class { events = /* @__PURE__ */ new Map(); sessions = /* @__PURE__ */ new Map(); eventTimeline = []; maxEvents = 1e3; maxSessions = 100; async storeEvent(event) { const eventKey = `${event.timestamp}:${event.eventId}`; this.events.set(eventKey, event); this.eventTimeline.push({ timestamp: Date.parse(event.timestamp), eventId: eventKey }); this.eventTimeline.sort((a, b) => b.timestamp - a.timestamp); if (this.eventTimeline.length > this.maxEvents) { const removed = this.eventTimeline.splice(this.maxEvents); removed.forEach((item) => this.events.delete(item.eventId)); } } async getRecentEvents(limit = 100) { const recent = this.eventTimeline.slice(0, limit); return recent.map((item) => this.events.get(item.eventId)).filter((event) => event !== void 0); } async getSessionEvents(sessionId) { const events = []; for (const event of this.events.values()) { if (event.sessionId === sessionId) { events.push(event); } } return events.sort( (a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp) ); } async storeSession(session) { this.sessions.set(session.sessionId, session); if (this.sessions.size > this.maxSessions) { const sortedSessions = Array.from(this.sessions.entries()).sort((a, b) => Date.parse(b[1].lastSeen) - Date.parse(a[1].lastSeen)); const toRemove = sortedSessions.slice(this.maxSessions); toRemove.forEach(([id]) => this.sessions.delete(id)); } } async getSession(sessionId) { return this.sessions.get(sessionId) || null; } async getRecentSessions(limit = 10) { const sorted = Array.from(this.sessions.values()).sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)); return sorted.slice(0, limit); } async cleanup(olderThan) { const cutoff = olderThan.getTime(); this.eventTimeline = this.eventTimeline.filter((item) => { if (item.timestamp < cutoff) { this.events.delete(item.eventId); return false; } return true; }); for (const [id, session] of this.sessions.entries()) { if (Date.parse(session.lastSeen) < cutoff) { this.sessions.delete(id); } } } }; // src/storage/redis-adapter.ts var RedisStorageAdapter = class { redis; ttl; keyPrefix = "agent-shield"; constructor(redis, ttl = 86400) { this.redis = redis; this.ttl = ttl; } eventKey(timestamp, eventId) { return `${this.keyPrefix}:events:${timestamp}:${eventId}`; } sessionKey(sessionId) { return `${this.keyPrefix}:sessions:${sessionId}`; } timelineKey() { return `${this.keyPrefix}:events:timeline`; } async storeEvent(event) { const key = this.eventKey(event.timestamp, event.eventId); await this.redis.setex(key, this.ttl, JSON.stringify(event)); await this.redis.zadd(this.timelineKey(), { score: Date.parse(event.timestamp), member: key }); const count = await this.redis.zcard(this.timelineKey()); if (count && count > 1e3) { await this.redis.zremrangebyrank(this.timelineKey(), 0, -1001); } } async getRecentEvents(limit = 100) { const keys = await this.redis.zrevrange(this.timelineKey(), 0, limit - 1); if (!keys || keys.length === 0) { return []; } const events = []; for (const key of keys) { const data = await this.redis.get(key); if (data) { try { const event = typeof data === "string" ? JSON.parse(data) : data; events.push(event); } catch (e) { console.error(`Failed to parse event ${key}:`, e); } } } return events; } async getSessionEvents(sessionId) { const keys = await this.redis.zrevrange(this.timelineKey(), 0, -1); if (!keys || keys.length === 0) { return []; } const events = []; for (const key of keys) { const data = await this.redis.get(key); if (data) { try { const event = typeof data === "string" ? JSON.parse(data) : data; if (event.sessionId === sessionId) { events.push(event); } } catch (e) { console.error(`Failed to parse event ${key}:`, e); } } } return events; } async storeSession(session) { const key = this.sessionKey(session.sessionId); const existing = await this.redis.get(key); if (existing) { const existingSession = typeof existing === "string" ? JSON.parse(existing) : existing; const methods = /* @__PURE__ */ new Set([ ...existingSession.verificationMethods || [], ...session.verificationMethods ]); const updatedSession = { ...existingSession, lastSeen: session.lastSeen, eventCount: session.eventCount, paths: Array.from(/* @__PURE__ */ new Set([...existingSession.paths, ...session.paths])), averageConfidence: session.averageConfidence, verificationMethods: Array.from(methods) }; await this.redis.setex(key, this.ttl, JSON.stringify(updatedSession)); } else { await this.redis.setex(key, this.ttl, JSON.stringify(session)); } } async getSession(sessionId) { const key = this.sessionKey(sessionId); const data = await this.redis.get(key); if (!data) { return null; } try { return typeof data === "string" ? JSON.parse(data) : data; } catch (e) { console.error(`Failed to parse session ${sessionId}:`, e); return null; } } async getRecentSessions(limit = 10) { const pattern = `${this.keyPrefix}:sessions:*`; const sessions = []; let cursor = 0; do { const [nextCursor, keys] = await this.redis.scan(cursor, { match: pattern, count: 100 }); cursor = parseInt(nextCursor); for (const key of keys) { const data = await this.redis.get(key); if (data) { try { const session = typeof data === "string" ? JSON.parse(data) : data; sessions.push(session); } catch (e) { console.error(`Failed to parse session from ${key}:`, e); } } } } while (cursor !== 0 && sessions.length < limit * 2); return sessions.sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)).slice(0, limit); } async cleanup(olderThan) { const cutoff = olderThan.getTime(); await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3)); } }; // src/storage/index.ts async function createStorageAdapter(config) { if (!config || config.type === "memory") { return new MemoryStorageAdapter(); } if (config.type === "custom" && config.custom) { return config.custom; } if (config.type === "redis" && config.redis) { try { const redisModuleName = "@upstash/redis"; const redisModule = await import( /* webpackIgnore: true */ redisModuleName ); const Redis = redisModule.Redis; const redis = new Redis({ url: config.redis.url, token: config.redis.token }); return new RedisStorageAdapter(redis, config.ttl); } catch (error) { console.warn( "[AgentShield] Redis storage requires @upstash/redis package. Install with: npm install @upstash/redis", "\nFalling back to memory storage." ); return new MemoryStorageAdapter(); } } return new MemoryStorageAdapter(); } // src/enhanced-middleware.ts var SessionManager = class { sessionLastActivity = /* @__PURE__ */ new Map(); generateSessionId(ipAddress, userAgent) { const now = Date.now(); const timeWindow = Math.floor(now / (5 * 60 * 1e3)); const baseKey = `${ipAddress || "unknown"}:${userAgent || "unknown"}`; const windowKey = `${baseKey}:${timeWindow}`; const lastActivity = this.sessionLastActivity.get(windowKey); const shouldCreateNewSession = !lastActivity || now - lastActivity > 3e4; this.sessionLastActivity.set(windowKey, now); if (this.sessionLastActivity.size > 100) { const cutoff = now - 6e5; for (const [key, time] of this.sessionLastActivity.entries()) { if (time < cutoff) { this.sessionLastActivity.delete(key); } } } const data = shouldCreateNewSession ? `${windowKey}:${now}` : `${windowKey}:${lastActivity}`; let hash = 0; for (let i = 0; i < data.length; i++) { const char = data.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash).toString(16).padStart(12, "0").substring(0, 12); } }; function createEnhancedAgentShieldMiddleware(config = {}) { let storageAdapter = null; let storageInitPromise = null; const getStorage = async () => { if (storageAdapter) return storageAdapter; if (storageInitPromise) return storageInitPromise; storageInitPromise = createStorageAdapter(config.storage).then((adapter) => { storageAdapter = adapter; return adapter; }); return storageInitPromise; }; let detector = null; let detectorInitPromise = null; let wasmConfidenceUtils = null; const getDetector = async (requestUrl) => { if (detector) { return detector; } if (detectorInitPromise) { await detectorInitPromise; return detector; } detectorInitPromise = (async () => { const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge"; if (isEdgeRuntime) { if (process.env.NODE_ENV !== "production") { console.debug("[AgentShield] Edge Runtime detected - using pattern detection"); } detector = new EdgeSafeDetector(); } else { try { try { const wasmUtils = await Promise.resolve().then(() => (init_wasm_confidence(), wasm_confidence_exports)); wasmConfidenceUtils = wasmUtils; const wasmAvailable = await wasmUtils.checkWasmAvailability(); if (wasmAvailable) { const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports)); if (process.env.NODE_ENV !== "production") { console.debug( "[AgentShield] \u2705 WASM support detected - enhanced detection enabled" ); } detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true }); if (requestUrl && "setBaseUrl" in detector) { detector.setBaseUrl(requestUrl); } } else { if (process.env.NODE_ENV !== "production") { console.debug( "[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)" ); } detector = new EdgeSafeDetector(); } } catch (wasmError) { if (process.env.NODE_ENV !== "production") { console.debug("[AgentShield] WASM utilities not available, using pattern detection"); } detector = new EdgeSafeDetector(); } } catch (error) { console.warn("[AgentShield] Failed to initialize enhanced detector, using fallback"); detector = new EdgeSafeDetector(); } } })(); await detectorInitPromise; return detector; }; const sessionManager = new SessionManager(); const sessionTrackingEnabled = config.sessionTracking?.enabled !== false; return async (request) => { const { pathname } = request.nextUrl; if (config.skipPaths?.some((path) => pathname.startsWith(path))) { return NextResponse.next(); } const userAgent = request.headers.get("user-agent"); const ipAddress = request.ip ?? request.headers.get("x-forwarded-for"); const url = new URL(request.url); const pathWithQuery = url.pathname + url.search; const context = { userAgent: userAgent || "", ipAddress: ipAddress || "", headers: Object.fromEntries(request.headers.entries()), url: pathWithQuery, method: request.method, timestamp: /* @__PURE__ */ new Date() }; const activeDetector = await getDetector(request.url); const result = await activeDetector.analyze(context); let finalConfidence = result.confidence; let verificationMethod = result.verificationMethod || "pattern"; if (result.isAgent && wasmConfidenceUtils) { const reasons = result.reasons || []; if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) { finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons); verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons); } } if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) { if (sessionTrackingEnabled) { const storage = await getStorage(); const sessionId = sessionManager.generateSessionId( ipAddress || void 0, userAgent || void 0 ); const event = { eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`, sessionId, timestamp: (/* @__PURE__ */ new Date()).toISOString(), agentType: result.detectedAgent?.type || "unknown", agentName: result.detectedAgent?.name || "Unknown", confidence: finalConfidence, path: pathWithQuery, ...userAgent && { userAgent }, ...ipAddress && { ipAddress }, method: request.method, detectionReasons: result.reasons || [], verificationMethod, detectionDetails: { patterns: result.detectedAgent?.patterns, behaviors: result.detectedAgent?.behaviors, fingerprintMatches: result.detectedAgent?.fingerprints } }; try { await storage.storeEvent(event); let session = await storage.getSession(sessionId); if (session) { session.lastSeen = event.timestamp; session.eventCount++; if (!session.paths.includes(pathWithQuery)) { session.paths.push(pathWithQuery); } session.averageConfidence = (session.averageConfidence * (session.eventCount - 1) + finalConfidence) / session.eventCount; if (!session.verificationMethods.includes(verificationMethod)) { session.verificationMethods.push(verificationMethod); } } else { session = { sessionId, ...ipAddress && { ipAddress }, ...userAgent && { userAgent }, agentType: result.detectedAgent?.type || "unknown", agentName: result.detectedAgent?.name || "Unknown", firstSeen: event.timestamp, lastSeen: event.timestamp, eventCount: 1, paths: [pathWithQuery], averageConfidence: finalConfidence, verificationMethods: [verificationMethod] }; } if (session) { await storage.storeSession(session); } } catch (error) { console.error("[AgentShield] Failed to store event:", error); } } if (config.onDetection) { await config.onDetection(result, context); } switch (config.onAgentDetected) { case "block": { const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {}; const response2 = NextResponse.json( { error: message, detected: true, confidence: finalConfidence }, { status } ); response2.headers.set("x-agentshield-detected", "true"); response2.headers.set( "x-agentshield-confidence", String(Math.round(finalConfidence * 100)) ); response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown"); response2.headers.set("x-agentshield-verification", verificationMethod); return response2; } case "log": { const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature"; if (isInteresting && process.env.NODE_ENV !== "production") { console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, { agent: result.detectedAgent?.name, confidence: `${(finalConfidence * 100).toFixed(0)}%`, path: pathWithQuery, verification: verificationMethod }); } break; } } } const response = NextResponse.next(); if (result.isAgent) { response.headers.set("x-agentshield-detected", "true"); response.headers.set("x-agentshield-confidence", String(Math.round(finalConfidence * 100))); response.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown"); response.headers.set("x-agentshield-verification", verificationMethod); if (finalConfidence > 0.9) { response.headers.set("x-ai-visitor", "true"); response.headers.set("x-ai-confidence", finalConfidence.toString()); response.headers.set("x-ai-verification", verificationMethod); } } return response; }; } export { createEnhancedAgentShieldMiddleware }; //# sourceMappingURL=enhanced-middleware.mjs.map //# sourceMappingURL=enhanced-middleware.mjs.map