@kya-os/agentshield-nextjs
Version:
Next.js middleware for AgentShield AI agent detection
1 lines • 18.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/policy.ts"],"names":[],"mappings":"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","file":"policy.mjs","sourcesContent":["/**\n * Policy Integration for agentshield-nextjs\n *\n * This module provides policy evaluation support for the Next.js middleware.\n * It can use:\n * - Local policy configuration (static)\n * - Fetched policy from AgentShield API (dynamic with caching)\n * - Fallback/default policies\n *\n * @example\n * ```typescript\n * import { createPolicyMiddleware } from '@kya-os/agentshield-nextjs/policy';\n *\n * export default createPolicyMiddleware({\n * policy: {\n * enabled: true,\n * defaultAction: 'allow',\n * thresholds: { confidenceThreshold: 80, confidenceAction: 'block' },\n * allowList: [{ clientName: 'ChatGPT' }],\n * },\n * });\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n evaluatePolicy,\n createEvaluationContext,\n createPolicyFetcher,\n matchPath,\n PolicyFetcher,\n PolicyConfigSchema,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n type PolicyFetcherConfig,\n type DetectionResult,\n} from '@kya-os/agentshield-shared';\n\n// Re-export shared policy types for convenience\nexport {\n evaluatePolicy,\n createEvaluationContext,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n} from '@kya-os/agentshield-shared';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Policy middleware configuration\n */\nexport interface PolicyMiddlewareConfig {\n /**\n * Local policy configuration (static)\n * If provided, this policy is used instead of fetching from API\n */\n policy?: Partial<PolicyConfig>;\n\n /**\n * Fetch policy from AgentShield API\n * Requires projectId and optionally an apiKey\n */\n fetchPolicy?: {\n /** Project ID to fetch policy for */\n projectId: string;\n /** API base URL (defaults to production) */\n apiUrl?: string;\n /** API key for authentication */\n apiKey?: string;\n /** Cache TTL in seconds (default: 300) */\n cacheTtlSeconds?: number;\n };\n\n /**\n * Fallback policy to use when fetch fails\n * Defaults to DEFAULT_POLICY (allow all)\n */\n fallbackPolicy?: Partial<PolicyConfig>;\n\n /**\n * Custom blocked response\n */\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n\n /**\n * Default redirect URL for redirect actions\n */\n redirectUrl?: string;\n\n /**\n * Callback when policy decision is made\n */\n onPolicyDecision?: (\n request: NextRequest,\n decision: PolicyEvaluationResult,\n context: PolicyEvaluationContext\n ) => void | Promise<void>;\n\n /**\n * Custom response builder for blocked requests\n */\n customBlockedResponse?: (\n request: NextRequest,\n decision: PolicyEvaluationResult\n ) => NextResponse | Promise<NextResponse>;\n\n /**\n * Whether to fail open (allow) on policy evaluation errors\n * Default: true (recommended for production)\n */\n failOpen?: boolean;\n\n /**\n * Enable debug logging\n */\n debug?: boolean;\n}\n\n/**\n * Combined middleware configuration with policy support\n */\nexport interface NextJSPolicyMiddlewareConfig extends PolicyMiddlewareConfig {\n /**\n * Paths to skip (in addition to policy excludedPaths)\n */\n skipPaths?: string[];\n\n /**\n * Only enforce on these paths (overrides policy includedPaths)\n */\n includePaths?: string[];\n}\n\n// ============================================================================\n// Policy Evaluation Helper\n// ============================================================================\n\n/**\n * Create policy evaluation context from detection result and request\n */\nexport function createContextFromDetection(\n detection: DetectionResult,\n request: NextRequest\n): PolicyEvaluationContext {\n return createEvaluationContext({\n agentType: detection.detectedAgent?.type,\n agentName: detection.detectedAgent?.name,\n agentVendor: detection.detectedAgent?.vendor,\n confidence: detection.confidence,\n riskLevel: detection.riskLevel,\n path: request.nextUrl.pathname,\n method: request.method,\n signatureVerified: detection.verificationMethod === 'signature',\n isAuthenticated: false, // TODO: integrate with auth\n userAgent: request.headers.get('user-agent') || undefined,\n });\n}\n\n/**\n * Evaluate policy for a detection result\n */\nexport function evaluatePolicyForDetection(\n detection: DetectionResult,\n request: NextRequest,\n policy: PolicyConfig\n): PolicyEvaluationResult {\n const context = createContextFromDetection(detection, request);\n return evaluatePolicy(policy, context);\n}\n\n// ============================================================================\n// Response Builders\n// ============================================================================\n\n/**\n * Build blocked response based on policy decision\n */\nexport function buildBlockedResponse(\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const status = config.blockedResponse?.status ?? 403;\n const message = config.blockedResponse?.message ?? decision.message ?? 'Access denied';\n\n const response = NextResponse.json(\n {\n error: message,\n code: 'POLICY_BLOCKED',\n reason: decision.reason,\n ruleId: decision.ruleId,\n matchType: decision.matchType,\n },\n { status }\n );\n\n // Add custom headers\n if (config.blockedResponse?.headers) {\n for (const [key, value] of Object.entries(config.blockedResponse.headers)) {\n response.headers.set(key, value);\n }\n }\n\n // Add AgentShield headers\n response.headers.set('X-AgentShield-Action', decision.action);\n response.headers.set('X-AgentShield-Reason', decision.reason);\n response.headers.set('X-AgentShield-MatchType', decision.matchType);\n\n return response;\n}\n\n/**\n * Build redirect response based on policy decision\n */\nexport function buildRedirectResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const redirectUrl = decision.redirectUrl || config.redirectUrl || '/blocked';\n const url = new URL(redirectUrl, request.url);\n\n // Add query params with policy info\n url.searchParams.set('reason', decision.reason);\n if (decision.ruleId) {\n url.searchParams.set('ruleId', decision.ruleId);\n }\n\n return NextResponse.redirect(url);\n}\n\n/**\n * Build challenge response (placeholder - future implementation)\n */\nexport function buildChallengeResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config);\n}\n\n// ============================================================================\n// Policy Handler\n// ============================================================================\n\n/**\n * Handle policy decision and return appropriate response\n */\nexport async function handlePolicyDecision(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n switch (decision.action) {\n case ENFORCEMENT_ACTIONS.BLOCK:\n if (config.customBlockedResponse) {\n return await config.customBlockedResponse(request, decision);\n }\n return buildBlockedResponse(decision, config);\n\n case ENFORCEMENT_ACTIONS.REDIRECT:\n return buildRedirectResponse(request, decision, config);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config);\n\n case ENFORCEMENT_ACTIONS.LOG:\n // LOG action always logs - that's its purpose\n // (debug flag controls verbose debugging output, not LOG action behavior)\n console.log('[AgentShield] Policy decision (log):', {\n path: request.nextUrl.pathname,\n action: decision.action,\n reason: decision.reason,\n matchType: decision.matchType,\n ruleId: decision.ruleId,\n });\n return null; // Continue to allow\n\n case ENFORCEMENT_ACTIONS.ALLOW:\n default:\n return null; // Continue\n }\n}\n\n// ============================================================================\n// Policy Fetcher Integration\n// ============================================================================\n\n// Cache fetchers by config to avoid recreating them, but also support\n// different configurations (different apiUrl, apiKey, etc.)\nconst fetcherCache = new Map<string, PolicyFetcher>();\n\n/**\n * Generate a cache key for fetcher config.\n * Uses ?? to distinguish between explicit 0 and undefined values.\n */\nfunction getFetcherCacheKey(config: NonNullable<PolicyMiddlewareConfig['fetchPolicy']>): string {\n return `${config.apiUrl ?? 'default'}:${config.apiKey ?? ''}:${config.cacheTtlSeconds ?? 'default'}`;\n}\n\n/**\n * Get or create policy fetcher for the given config\n */\nfunction getPolicyFetcher(config: PolicyMiddlewareConfig['fetchPolicy']): PolicyFetcher {\n if (!config) {\n throw new Error('fetchPolicy config required');\n }\n\n const cacheKey = getFetcherCacheKey(config);\n let fetcher = fetcherCache.get(cacheKey);\n\n if (!fetcher) {\n const fetcherConfig: PolicyFetcherConfig = {\n apiBaseUrl: config.apiUrl || 'https://kya.vouched.id',\n apiKey: config.apiKey,\n cacheTtlSeconds: config.cacheTtlSeconds,\n };\n fetcher = createPolicyFetcher(fetcherConfig);\n fetcherCache.set(cacheKey, fetcher);\n }\n\n return fetcher;\n}\n\n/**\n * Get policy (local, fetched, or fallback)\n */\nexport async function getPolicy(config: PolicyMiddlewareConfig): Promise<PolicyConfig> {\n // Use local policy if provided\n if (config.policy) {\n return PolicyConfigSchema.parse({ ...DEFAULT_POLICY, ...config.policy });\n }\n\n // Fetch from API if configured\n if (config.fetchPolicy) {\n try {\n const fetcher = getPolicyFetcher(config.fetchPolicy);\n return await fetcher.getPolicy(config.fetchPolicy.projectId);\n } catch (error) {\n if (config.debug) {\n console.warn('[AgentShield] Policy fetch failed, using fallback:', error);\n }\n // Return fallback policy\n return PolicyConfigSchema.parse({\n ...DEFAULT_POLICY,\n ...(config.fallbackPolicy || {}),\n });\n }\n }\n\n // No policy configured - return default (allow all)\n return PolicyConfigSchema.parse(DEFAULT_POLICY);\n}\n\n// ============================================================================\n// Standalone Policy Middleware\n// ============================================================================\n\n/**\n * Apply policy to a detection result\n *\n * This function can be used standalone to evaluate policy after detection.\n * Supports extended config with skipPaths and includePaths for path-based filtering.\n *\n * @example\n * ```typescript\n * const result = await detector.analyze(context);\n * const response = await applyPolicy(request, result, {\n * policy: { thresholds: { confidenceThreshold: 80 } },\n * skipPaths: ['/health', '/api/public/*'],\n * includePaths: ['/api/*'],\n * });\n *\n * if (response) {\n * return response; // Policy blocked the request\n * }\n * ```\n */\nexport async function applyPolicy(\n request: NextRequest,\n detection: DetectionResult,\n config: NextJSPolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n try {\n const path = request.nextUrl.pathname;\n\n // Check skipPaths - if path matches any skip pattern, allow through\n if (config.skipPaths?.some((pattern) => matchPath(path, pattern))) {\n return null; // Skip policy enforcement for this path\n }\n\n // Check includePaths - if defined, path must match at least one pattern\n if (config.includePaths && config.includePaths.length > 0) {\n if (!config.includePaths.some((pattern) => matchPath(path, pattern))) {\n return null; // Path not in included paths, skip policy enforcement\n }\n }\n\n // Get policy\n const policy = await getPolicy(config);\n\n // Create context and evaluate\n const context = createContextFromDetection(detection, request);\n const decision = evaluatePolicy(policy, context);\n\n // Call decision callback if provided\n if (config.onPolicyDecision) {\n await config.onPolicyDecision(request, decision, context);\n }\n\n // Handle decision\n return await handlePolicyDecision(request, decision, config);\n } catch (error) {\n if (config.debug) {\n console.error('[AgentShield] Policy evaluation error:', error);\n }\n\n if (config.failOpen !== false) {\n return null; // Allow on error\n }\n\n // Fail closed\n return NextResponse.json(\n { error: 'Security check failed', code: 'POLICY_ERROR' },\n { status: 503 }\n );\n }\n}\n"]}