UNPKG

@kya-os/agentshield-nextjs

Version:

Next.js middleware for AgentShield AI agent detection

1 lines 12.5 kB
{"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.mjs","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/agentshield';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey ||\n process.env.AGENTSHIELD_SECRET ||\n 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !result.isAgent) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(\n Array.from(obfuscated, byte => String.fromCharCode(byte)).join('')\n );\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), c => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['x-agentshield-session-agent'];\n const confidence = headers['x-agentshield-session-confidence'];\n const sessionId = headers['x-agentshield-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('X-AgentShield-Session-Agent', session.agent);\n response.setHeader(\n 'X-AgentShield-Session-Confidence',\n session.confidence.toString()\n );\n response.setHeader('X-AgentShield-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('x-agentshield-session-agent', session.agent);\n response.headers.set(\n 'x-agentshield-session-confidence',\n session.confidence.toString()\n );\n response.headers.set('x-agentshield-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}