UNPKG

@kya-os/agentshield-nextjs

Version:

Next.js middleware for AgentShield AI agent detection

253 lines (250 loc) 9.34 kB
'use strict'; var server = require('next/server'); // src/edge/index.ts async function patternDetect(input) { const userAgent = input.userAgent?.toLowerCase() || ""; const reasons = []; let confidence = 0; let detectedAgent; const patterns = [ { pattern: /chatgpt-user/i, name: "ChatGPT", confidence: 95 }, { pattern: /claude-web/i, name: "Claude", confidence: 95 }, { pattern: /claude-?bot/i, name: "ClaudeBot", confidence: 95 }, { pattern: /anthropic/i, name: "Claude", confidence: 90 }, { pattern: /gptbot/i, name: "GPTBot", confidence: 90 }, { pattern: /perplexity/i, name: "Perplexity", confidence: 90 }, { pattern: /openai/i, name: "OpenAI", confidence: 85 }, { pattern: /copilot/i, name: "Copilot", confidence: 85 }, { pattern: /gemini/i, name: "Gemini", confidence: 85 }, { pattern: /bard/i, name: "Bard", confidence: 85 }, // HTTP client libraries (often used by AI agents/scrapers) { pattern: /^axios\//i, name: "HTTP Client (axios)", confidence: 75 }, { pattern: /^node-fetch\//i, name: "HTTP Client (node-fetch)", confidence: 75 }, { pattern: /^got\//i, name: "HTTP Client (got)", confidence: 75 }, { pattern: /^python-requests\//i, name: "HTTP Client (requests)", confidence: 75 }, { pattern: /^python-urllib/i, name: "HTTP Client (urllib)", confidence: 75 }, { pattern: /^curl\//i, name: "HTTP Client (curl)", confidence: 60 }, { pattern: /^wget\//i, name: "HTTP Client (wget)", confidence: 60 }, { pattern: /^httpie\//i, name: "HTTP Client (httpie)", confidence: 70 }, { pattern: /^undici\//i, name: "HTTP Client (undici)", confidence: 75 } ]; for (const { pattern, name, confidence: patternConfidence } of patterns) { if (pattern.test(userAgent)) { confidence = patternConfidence; detectedAgent = { type: name.toLowerCase(), name }; reasons.push(`known_pattern:${name.toLowerCase()}`); break; } } const aiHeaders = ["x-openai-", "x-anthropic-", "x-ai-", "signature-agent"]; for (const [key] of Object.entries(input.headers)) { if (aiHeaders.some((prefix) => key.toLowerCase().startsWith(prefix))) { confidence = Math.max(confidence, 45); reasons.push(`ai_headers:${key}`); break; } } return { isAgent: confidence > 30, confidence, detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" }, signals: [], ...detectedAgent && { detectedAgent }, reasons, verificationMethod: "pattern", forgeabilityRisk: confidence > 80 ? "medium" : "high", timestamp: /* @__PURE__ */ new Date() }; } async function createDetector(config) { let detector; if (config.wasmModule) { try { const { StaticWasmLoader, WasmDetector, PolicyLoader } = await import('@kya-os/agentshield-wasm-runtime/edge'); const loader = new StaticWasmLoader(config.wasmModule); const policyLoader = config.apiKey ? new PolicyLoader({ apiUrl: config.policyApiUrl }) : void 0; const wasmDetector = new WasmDetector(loader, policyLoader, { apiKey: config.apiKey, debug: config.debug }); await wasmDetector.ensureReady(); if (config.debug) { console.debug("[AgentShield] WASM detector initialized in Edge Runtime", { policyEnabled: !!config.apiKey }); } detector = { detect: async (input) => { const result = await wasmDetector.detect(input); return { isAgent: result.isAgent, confidence: result.confidence, detectionClass: result.detectionClass, detectedAgent: result.detectedAgent, verificationMethod: result.verificationMethod, forgeabilityRisk: result.forgeabilityRisk, reasons: result.reasons, timestamp: result.timestamp, signals: [], // Required by DetectionResult, empty for WASM results shouldBlock: result.shouldBlock, blockReason: result.blockReason }; }, isReady: () => wasmDetector.isReady() }; } catch (error) { if (config.debug) { console.warn("[AgentShield] WASM runtime not available, using pattern detection:", error); } detector = { detect: patternDetect, isReady: () => true }; } } else { if (config.debug) { console.debug("[AgentShield] No WASM module provided, using pattern detection"); } detector = { detect: patternDetect, isReady: () => true }; } return detector; } function createEdgeMiddleware(config = {}) { let detectorPromise = null; const { onAgentDetected = "log", onDetection, skipPaths = [], confidenceThreshold = 70, blockedResponse = { status: 403, message: "Access denied: Automated agent detected", headers: { "Content-Type": "application/json" } }, redirectUrl = "/blocked", rewriteUrl = "/blocked", debug = false } = config; return async (request) => { try { if (!detectorPromise) { detectorPromise = createDetector({ ...config, debug }); } const detector = await detectorPromise; const shouldSkip = skipPaths.some((pattern) => { if (typeof pattern === "string") { return request.nextUrl.pathname.startsWith(pattern); } return pattern.test(request.nextUrl.pathname); }); if (shouldSkip) { return server.NextResponse.next(); } const url = new URL(request.url); const xForwardedFor = request.headers.get("x-forwarded-for"); const clientIp = xForwardedFor?.split(",")[0]?.trim(); const input = { userAgent: request.headers.get("user-agent") || void 0, ipAddress: request.ip || clientIp || void 0, headers: Object.fromEntries(request.headers.entries()), url: url.pathname + url.search, method: request.method }; const result = await detector.detect(input); if (result.shouldBlock) { if (debug) { console.debug("[AgentShield] Blocked by policy", { reason: result.blockReason, agent: result.detectedAgent?.name, confidence: result.confidence, pathname: request.nextUrl.pathname }); } if (onDetection) { const customResponse = await onDetection(request, result); if (customResponse) { return customResponse; } } return server.NextResponse.json( { error: "Access denied by policy", reason: result.blockReason, detected: true, confidence: result.confidence, agent: result.detectedAgent?.name, timestamp: result.timestamp }, { status: 403, headers: { "Content-Type": "application/json" } } ); } if (result.shouldBlock !== false && result.isAgent && result.confidence >= confidenceThreshold) { if (onDetection) { const customResponse = await onDetection(request, result); if (customResponse) { return customResponse; } } switch (onAgentDetected) { case "block": { const response2 = server.NextResponse.json( { error: blockedResponse.message, detected: true, confidence: result.confidence, timestamp: result.timestamp }, { status: blockedResponse.status } ); if (blockedResponse.headers) { Object.entries(blockedResponse.headers).forEach(([key, value]) => { response2.headers.set(key, value); }); } return response2; } case "redirect": return server.NextResponse.redirect(new URL(redirectUrl, request.url)); case "rewrite": return server.NextResponse.rewrite(new URL(rewriteUrl, request.url)); case "log": if (debug || process.env.NODE_ENV !== "production") { console.debug("AgentShield: Agent detected", { ipAddress: input.ipAddress, userAgent: input.userAgent, confidence: result.confidence, agent: result.detectedAgent?.name, pathname: request.nextUrl.pathname }); } break; case "allow": default: break; } } const response = server.NextResponse.next(); response.headers.set("x-agentshield-detected", result.isAgent.toString()); response.headers.set("x-agentshield-confidence", result.confidence.toString()); if (result.detectedAgent?.name) { response.headers.set("x-agentshield-agent", result.detectedAgent.name); } return response; } catch (error) { console.error("AgentShield middleware error:", error); return server.NextResponse.next(); } }; } exports.createEdgeMiddleware = createEdgeMiddleware; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map