@kya-os/agentshield-nextjs 0.2.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-client.d.mts +6 -1
  3. package/dist/api-client.d.ts +6 -1
  4. package/dist/api-client.js +1 -1
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/api-client.mjs +1 -1
  7. package/dist/api-client.mjs.map +1 -1
  8. package/dist/api-middleware.d.mts +13 -0
  9. package/dist/api-middleware.d.ts +13 -0
  10. package/dist/api-middleware.js +146 -24
  11. package/dist/api-middleware.js.map +1 -1
  12. package/dist/api-middleware.mjs +146 -24
  13. package/dist/api-middleware.mjs.map +1 -1
  14. package/dist/create-middleware.js +565 -487
  15. package/dist/create-middleware.js.map +1 -1
  16. package/dist/create-middleware.mjs +565 -487
  17. package/dist/create-middleware.mjs.map +1 -1
  18. package/dist/edge/index.js +69 -46
  19. package/dist/edge/index.js.map +1 -1
  20. package/dist/edge/index.mjs +69 -46
  21. package/dist/edge/index.mjs.map +1 -1
  22. package/dist/edge-detector-wrapper.js +9 -1
  23. package/dist/edge-detector-wrapper.js.map +1 -1
  24. package/dist/edge-detector-wrapper.mjs +9 -1
  25. package/dist/edge-detector-wrapper.mjs.map +1 -1
  26. package/dist/edge-runtime-loader.d.mts +1 -0
  27. package/dist/edge-runtime-loader.d.ts +1 -0
  28. package/dist/edge-runtime-loader.js +19 -3
  29. package/dist/edge-runtime-loader.js.map +1 -1
  30. package/dist/edge-runtime-loader.mjs +19 -3
  31. package/dist/edge-runtime-loader.mjs.map +1 -1
  32. package/dist/edge-wasm-middleware.d.mts +1 -0
  33. package/dist/edge-wasm-middleware.d.ts +1 -0
  34. package/dist/edge-wasm-middleware.js +10 -2
  35. package/dist/edge-wasm-middleware.js.map +1 -1
  36. package/dist/edge-wasm-middleware.mjs +11 -3
  37. package/dist/edge-wasm-middleware.mjs.map +1 -1
  38. package/dist/enhanced-middleware.js +48 -20
  39. package/dist/enhanced-middleware.js.map +1 -1
  40. package/dist/enhanced-middleware.mjs +49 -21
  41. package/dist/enhanced-middleware.mjs.map +1 -1
  42. package/dist/index.d.mts +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.js +260 -107
  45. package/dist/index.js.map +1 -1
  46. package/dist/index.mjs +261 -108
  47. package/dist/index.mjs.map +1 -1
  48. package/dist/middleware.js +565 -487
  49. package/dist/middleware.js.map +1 -1
  50. package/dist/middleware.mjs +565 -487
  51. package/dist/middleware.mjs.map +1 -1
  52. package/dist/policy.d.mts +16 -4
  53. package/dist/policy.d.ts +16 -4
  54. package/dist/policy.js +14 -10
  55. package/dist/policy.js.map +1 -1
  56. package/dist/policy.mjs +14 -10
  57. package/dist/policy.mjs.map +1 -1
  58. package/dist/session-tracker.js +13 -19
  59. package/dist/session-tracker.js.map +1 -1
  60. package/dist/session-tracker.mjs +13 -19
  61. package/dist/session-tracker.mjs.map +1 -1
  62. package/dist/signature-verifier.js +9 -1
  63. package/dist/signature-verifier.js.map +1 -1
  64. package/dist/signature-verifier.mjs +9 -1
  65. package/dist/signature-verifier.mjs.map +1 -1
  66. package/dist/wasm-middleware.d.mts +1 -0
  67. package/dist/wasm-middleware.d.ts +1 -0
  68. package/dist/wasm-middleware.js +15 -15
  69. package/dist/wasm-middleware.js.map +1 -1
  70. package/dist/wasm-middleware.mjs +15 -15
  71. package/dist/wasm-middleware.mjs.map +1 -1
  72. package/package.json +5 -5
  73. package/wasm/agentshield_wasm.d.ts +2 -2
  74. package/wasm/agentshield_wasm_bg.wasm +0 -0
package/dist/policy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { DetectionResult, PolicyConfig, PolicyEvaluationResult, PolicyEvaluationContext } from '@kya-os/agentshield-shared';
2
+ import { PolicyConfig, PolicyEvaluationResult, PolicyEvaluationContext, DetectionResult } from '@kya-os/agentshield-shared';
3
3
  export { DEFAULT_POLICY, ENFORCEMENT_ACTIONS, PolicyConfig, PolicyEvaluationContext, PolicyEvaluationResult, createEvaluationContext, evaluatePolicy } from '@kya-os/agentshield-shared';
4
4
 
5
5
  /**
@@ -112,15 +112,27 @@ declare function buildBlockedResponse(decision: PolicyEvaluationResult, config:
112
112
  /**
113
113
  * Build redirect response based on policy decision
114
114
  */
115
- declare function buildRedirectResponse(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig): NextResponse;
115
+ declare function buildRedirectResponse(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig, detection?: {
116
+ detectedAgent?: {
117
+ name?: string;
118
+ };
119
+ }): NextResponse;
116
120
  /**
117
121
  * Build challenge response (placeholder - future implementation)
118
122
  */
119
- declare function buildChallengeResponse(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig): NextResponse;
123
+ declare function buildChallengeResponse(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig, detection?: {
124
+ detectedAgent?: {
125
+ name?: string;
126
+ };
127
+ }): NextResponse;
120
128
  /**
121
129
  * Handle policy decision and return appropriate response
122
130
  */
123
- declare function handlePolicyDecision(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig): Promise<NextResponse | null>;
131
+ declare function handlePolicyDecision(request: NextRequest, decision: PolicyEvaluationResult, config: PolicyMiddlewareConfig, detection?: {
132
+ detectedAgent?: {
133
+ name?: string;
134
+ };
135
+ }): Promise<NextResponse | null>;
124
136
  /**
125
137
  * Get policy (local, fetched, or fallback)
126
138
  */
package/dist/policy.js CHANGED
@@ -41,24 +41,28 @@ function buildBlockedResponse(decision, config) {
41
41
  response.headers.set(key, value);
42
42
  }
43
43
  }
44
- response.headers.set("X-AgentShield-Action", decision.action);
45
- response.headers.set("X-AgentShield-Reason", decision.reason);
46
- response.headers.set("X-AgentShield-MatchType", decision.matchType);
44
+ response.headers.set("KYA-Action", decision.action);
45
+ response.headers.set("KYA-Reason", decision.reason);
46
+ response.headers.set("KYA-Match-Type", decision.matchType);
47
47
  return response;
48
48
  }
49
- function buildRedirectResponse(request, decision, config) {
49
+ function buildRedirectResponse(request, decision, config, detection) {
50
50
  const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
51
51
  const url = new URL(redirectUrl, request.url);
52
52
  url.searchParams.set("reason", decision.reason);
53
53
  if (decision.ruleId) {
54
54
  url.searchParams.set("ruleId", decision.ruleId);
55
55
  }
56
+ const agentName = detection?.detectedAgent?.name;
57
+ if (agentName && !url.searchParams.has("agent")) {
58
+ url.searchParams.set("agent", agentName.toLowerCase());
59
+ }
56
60
  return server.NextResponse.redirect(url);
57
61
  }
58
- function buildChallengeResponse(request, decision, config) {
59
- return buildRedirectResponse(request, decision, config);
62
+ function buildChallengeResponse(request, decision, config, detection) {
63
+ return buildRedirectResponse(request, decision, config, detection);
60
64
  }
61
- async function handlePolicyDecision(request, decision, config) {
65
+ async function handlePolicyDecision(request, decision, config, detection) {
62
66
  switch (decision.action) {
63
67
  case agentshieldShared.ENFORCEMENT_ACTIONS.BLOCK:
64
68
  if (config.customBlockedResponse) {
@@ -66,9 +70,9 @@ async function handlePolicyDecision(request, decision, config) {
66
70
  }
67
71
  return buildBlockedResponse(decision, config);
68
72
  case agentshieldShared.ENFORCEMENT_ACTIONS.REDIRECT:
69
- return buildRedirectResponse(request, decision, config);
73
+ return buildRedirectResponse(request, decision, config, detection);
70
74
  case agentshieldShared.ENFORCEMENT_ACTIONS.CHALLENGE:
71
- return buildChallengeResponse(request, decision, config);
75
+ return buildChallengeResponse(request, decision, config, detection);
72
76
  case agentshieldShared.ENFORCEMENT_ACTIONS.LOG:
73
77
  console.log("[AgentShield] Policy decision (log):", {
74
78
  path: request.nextUrl.pathname,
@@ -142,7 +146,7 @@ async function applyPolicy(request, detection, config) {
142
146
  if (config.onPolicyDecision) {
143
147
  await config.onPolicyDecision(request, decision, context);
144
148
  }
145
- return await handlePolicyDecision(request, decision, config);
149
+ return await handlePolicyDecision(request, decision, config, detection);
146
150
  } catch (error) {
147
151
  if (config.debug) {
148
152
  console.error("[AgentShield] Policy evaluation error:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/policy.ts"],"names":["createEvaluationContext","evaluatePolicy","NextResponse","ENFORCEMENT_ACTIONS","createPolicyFetcher","PolicyConfigSchema","DEFAULT_POLICY","matchPath"],"mappings":"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","file":"policy.js","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"]}
1
+ {"version":3,"sources":["../src/policy.ts"],"names":["createEvaluationContext","evaluatePolicy","NextResponse","ENFORCEMENT_ACTIONS","createPolicyFetcher","PolicyConfigSchema","DEFAULT_POLICY","matchPath"],"mappings":"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","file":"policy.js","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('KYA-Action', decision.action);\n response.headers.set('KYA-Reason', decision.reason);\n response.headers.set('KYA-Match-Type', 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 detection?: { detectedAgent?: { name?: string } }\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 const agentName = detection?.detectedAgent?.name;\n if (agentName && !url.searchParams.has('agent')) {\n url.searchParams.set('agent', agentName.toLowerCase());\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 detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config, detection);\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 detection?: { detectedAgent?: { name?: string } }\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, detection);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config, detection);\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 — pass detection through so redirect can append ?agent=\n return await handlePolicyDecision(request, decision, config, detection);\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"]}
package/dist/policy.mjs CHANGED
@@ -40,24 +40,28 @@ function buildBlockedResponse(decision, config) {
40
40
  response.headers.set(key, value);
41
41
  }
42
42
  }
43
- response.headers.set("X-AgentShield-Action", decision.action);
44
- response.headers.set("X-AgentShield-Reason", decision.reason);
45
- response.headers.set("X-AgentShield-MatchType", decision.matchType);
43
+ response.headers.set("KYA-Action", decision.action);
44
+ response.headers.set("KYA-Reason", decision.reason);
45
+ response.headers.set("KYA-Match-Type", decision.matchType);
46
46
  return response;
47
47
  }
48
- function buildRedirectResponse(request, decision, config) {
48
+ function buildRedirectResponse(request, decision, config, detection) {
49
49
  const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
50
50
  const url = new URL(redirectUrl, request.url);
51
51
  url.searchParams.set("reason", decision.reason);
52
52
  if (decision.ruleId) {
53
53
  url.searchParams.set("ruleId", decision.ruleId);
54
54
  }
55
+ const agentName = detection?.detectedAgent?.name;
56
+ if (agentName && !url.searchParams.has("agent")) {
57
+ url.searchParams.set("agent", agentName.toLowerCase());
58
+ }
55
59
  return NextResponse.redirect(url);
56
60
  }
57
- function buildChallengeResponse(request, decision, config) {
58
- return buildRedirectResponse(request, decision, config);
61
+ function buildChallengeResponse(request, decision, config, detection) {
62
+ return buildRedirectResponse(request, decision, config, detection);
59
63
  }
60
- async function handlePolicyDecision(request, decision, config) {
64
+ async function handlePolicyDecision(request, decision, config, detection) {
61
65
  switch (decision.action) {
62
66
  case ENFORCEMENT_ACTIONS.BLOCK:
63
67
  if (config.customBlockedResponse) {
@@ -65,9 +69,9 @@ async function handlePolicyDecision(request, decision, config) {
65
69
  }
66
70
  return buildBlockedResponse(decision, config);
67
71
  case ENFORCEMENT_ACTIONS.REDIRECT:
68
- return buildRedirectResponse(request, decision, config);
72
+ return buildRedirectResponse(request, decision, config, detection);
69
73
  case ENFORCEMENT_ACTIONS.CHALLENGE:
70
- return buildChallengeResponse(request, decision, config);
74
+ return buildChallengeResponse(request, decision, config, detection);
71
75
  case ENFORCEMENT_ACTIONS.LOG:
72
76
  console.log("[AgentShield] Policy decision (log):", {
73
77
  path: request.nextUrl.pathname,
@@ -141,7 +145,7 @@ async function applyPolicy(request, detection, config) {
141
145
  if (config.onPolicyDecision) {
142
146
  await config.onPolicyDecision(request, decision, context);
143
147
  }
144
- return await handlePolicyDecision(request, decision, config);
148
+ return await handlePolicyDecision(request, decision, config, detection);
145
149
  } catch (error) {
146
150
  if (config.debug) {
147
151
  console.error("[AgentShield] Policy evaluation error:", error);
@@ -1 +1 @@
1
- {"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"]}
1
+ {"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('KYA-Action', decision.action);\n response.headers.set('KYA-Reason', decision.reason);\n response.headers.set('KYA-Match-Type', 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 detection?: { detectedAgent?: { name?: string } }\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 const agentName = detection?.detectedAgent?.name;\n if (agentName && !url.searchParams.has('agent')) {\n url.searchParams.set('agent', agentName.toLowerCase());\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 detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config, detection);\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 detection?: { detectedAgent?: { name?: string } }\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, detection);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config, detection);\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 — pass detection through so redirect can append ?agent=\n return await handlePolicyDecision(request, decision, config, detection);\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"]}
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var agentshieldShared = require('@kya-os/agentshield-shared');
4
+
3
5
  // src/session-tracker.ts
4
6
  var EdgeSessionTracker = class {
5
7
  config;
@@ -17,7 +19,7 @@ var EdgeSessionTracker = class {
17
19
  */
18
20
  async track(_request, response, result) {
19
21
  try {
20
- if (!this.config.enabled || !result.isAgent) {
22
+ if (!this.config.enabled || !agentshieldShared.shouldEnforce(result)) {
21
23
  return response;
22
24
  }
23
25
  const sessionData = {
@@ -92,9 +94,7 @@ var EdgeSessionTracker = class {
92
94
  for (let i = 0; i < encoded.length; i++) {
93
95
  obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
94
96
  }
95
- return btoa(
96
- Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
97
- );
97
+ return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
98
98
  } catch (error) {
99
99
  return btoa(data);
100
100
  }
@@ -119,9 +119,9 @@ var EdgeSessionTracker = class {
119
119
  var StatelessSessionChecker = class {
120
120
  static check(headers) {
121
121
  try {
122
- const agent = headers["x-agentshield-session-agent"];
123
- const confidence = headers["x-agentshield-session-confidence"];
124
- const sessionId = headers["x-agentshield-session-id"];
122
+ const agent = headers["kya-session-agent"];
123
+ const confidence = headers["kya-session-confidence"];
124
+ const sessionId = headers["kya-session-id"];
125
125
  if (agent && confidence && sessionId) {
126
126
  return {
127
127
  id: sessionId,
@@ -151,19 +151,13 @@ var StatelessSessionChecker = class {
151
151
  static setHeaders(response, session) {
152
152
  try {
153
153
  if (response.setHeader) {
154
- response.setHeader("X-AgentShield-Session-Agent", session.agent);
155
- response.setHeader(
156
- "X-AgentShield-Session-Confidence",
157
- session.confidence.toString()
158
- );
159
- response.setHeader("X-AgentShield-Session-Id", session.id);
154
+ response.setHeader("KYA-Session-Agent", session.agent);
155
+ response.setHeader("KYA-Session-Confidence", session.confidence.toString());
156
+ response.setHeader("KYA-Session-Id", session.id);
160
157
  } else if (response.headers && response.headers.set) {
161
- response.headers.set("x-agentshield-session-agent", session.agent);
162
- response.headers.set(
163
- "x-agentshield-session-confidence",
164
- session.confidence.toString()
165
- );
166
- response.headers.set("x-agentshield-session-id", session.id);
158
+ response.headers.set("kya-session-agent", session.agent);
159
+ response.headers.set("kya-session-confidence", session.confidence.toString());
160
+ response.headers.set("kya-session-id", session.id);
167
161
  }
168
162
  } catch {
169
163
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.js","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-shared';\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"]}
1
+ {"version":3,"sources":["../src/session-tracker.ts"],"names":["shouldEnforce"],"mappings":"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","file":"session-tracker.js","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-shared';\nimport { shouldEnforce } from '@kya-os/agentshield-shared';\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 || process.env.AGENTSHIELD_SECRET || '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 || !shouldEnforce(result)) {\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(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(''));\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['kya-session-agent'];\n const confidence = headers['kya-session-confidence'];\n const sessionId = headers['kya-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('KYA-Session-Agent', session.agent);\n response.setHeader('KYA-Session-Confidence', session.confidence.toString());\n response.setHeader('KYA-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('kya-session-agent', session.agent);\n response.headers.set('kya-session-confidence', session.confidence.toString());\n response.headers.set('kya-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { shouldEnforce } from '@kya-os/agentshield-shared';
2
+
1
3
  // src/session-tracker.ts
2
4
  var EdgeSessionTracker = class {
3
5
  config;
@@ -15,7 +17,7 @@ var EdgeSessionTracker = class {
15
17
  */
16
18
  async track(_request, response, result) {
17
19
  try {
18
- if (!this.config.enabled || !result.isAgent) {
20
+ if (!this.config.enabled || !shouldEnforce(result)) {
19
21
  return response;
20
22
  }
21
23
  const sessionData = {
@@ -90,9 +92,7 @@ var EdgeSessionTracker = class {
90
92
  for (let i = 0; i < encoded.length; i++) {
91
93
  obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
92
94
  }
93
- return btoa(
94
- Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
95
- );
95
+ return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
96
96
  } catch (error) {
97
97
  return btoa(data);
98
98
  }
@@ -117,9 +117,9 @@ var EdgeSessionTracker = class {
117
117
  var StatelessSessionChecker = class {
118
118
  static check(headers) {
119
119
  try {
120
- const agent = headers["x-agentshield-session-agent"];
121
- const confidence = headers["x-agentshield-session-confidence"];
122
- const sessionId = headers["x-agentshield-session-id"];
120
+ const agent = headers["kya-session-agent"];
121
+ const confidence = headers["kya-session-confidence"];
122
+ const sessionId = headers["kya-session-id"];
123
123
  if (agent && confidence && sessionId) {
124
124
  return {
125
125
  id: sessionId,
@@ -149,19 +149,13 @@ var StatelessSessionChecker = class {
149
149
  static setHeaders(response, session) {
150
150
  try {
151
151
  if (response.setHeader) {
152
- response.setHeader("X-AgentShield-Session-Agent", session.agent);
153
- response.setHeader(
154
- "X-AgentShield-Session-Confidence",
155
- session.confidence.toString()
156
- );
157
- response.setHeader("X-AgentShield-Session-Id", session.id);
152
+ response.setHeader("KYA-Session-Agent", session.agent);
153
+ response.setHeader("KYA-Session-Confidence", session.confidence.toString());
154
+ response.setHeader("KYA-Session-Id", session.id);
158
155
  } else if (response.headers && response.headers.set) {
159
- response.headers.set("x-agentshield-session-agent", session.agent);
160
- response.headers.set(
161
- "x-agentshield-session-confidence",
162
- session.confidence.toString()
163
- );
164
- response.headers.set("x-agentshield-session-id", session.id);
156
+ response.headers.set("kya-session-agent", session.agent);
157
+ response.headers.set("kya-session-confidence", session.confidence.toString());
158
+ response.headers.set("kya-session-id", session.id);
165
159
  }
166
160
  } catch {
167
161
  }
@@ -1 +1 @@
1
- {"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-shared';\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"]}
1
+ {"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":";;;AAwBO,IAAM,qBAAN,MAAyB;AAAA,EACb,MAAA;AAAA,EAEjB,YAAY,MAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,UAAA,EAAY,OAAO,UAAA,IAAc,uBAAA;AAAA,MACjC,YAAA,EAAc,OAAO,YAAA,IAAgB,IAAA;AAAA;AAAA,MACrC,aAAA,EACE,MAAA,CAAO,aAAA,IAAiB,OAAA,CAAQ,IAAI,kBAAA,IAAsB;AAAA,KAC9D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,CACJ,QAAA,EACA,QAAA,EACA,MAAA,EACuB;AACvB,IAAA,IAAI;AACF,MAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,WAAW,CAAC,aAAA,CAAc,MAAM,CAAA,EAAG;AAClD,QAAA,OAAO,QAAA;AAAA,MACT;AAEA,MAAA,MAAM,WAAA,GAA2B;AAAA,QAC/B,EAAA,EAAI,OAAO,UAAA,EAAW;AAAA,QACtB,KAAA,EAAO,MAAA,CAAO,aAAA,EAAe,IAAA,IAAQ,SAAA;AAAA,QACrC,YAAY,MAAA,CAAO,UAAA;AAAA,QACnB,UAAA,EAAY,KAAK,GAAA,EAAI;AAAA,QACrB,SAAS,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,OAAO,YAAA,GAAe;AAAA,OACnD;AAGA,MAAA,MAAM,YAAY,MAAM,IAAA,CAAK,QAAQ,IAAA,CAAK,SAAA,CAAU,WAAW,CAAC,CAAA;AAGhE,MAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,YAAY,SAAA,EAAW;AAAA,QACtD,QAAA,EAAU,IAAA;AAAA,QACV,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA;AAAA,QACjC,QAAA,EAAU,KAAA;AAAA,QACV,MAAA,EAAQ,KAAK,MAAA,CAAO,YAAA;AAAA,QACpB,IAAA,EAAM;AAAA,OACP,CAAA;AAED,MAAA,OAAO,QAAA;AAAA,IACT,SAAS,KAAA,EAAO;AAEd,MAAA,IAAI,OAAA,CAAQ,IAAI,iBAAA,EAAmB;AACjC,QAAA,OAAA,CAAQ,IAAA,CAAK,yCAAyC,KAAK,CAAA;AAAA,MAC7D;AACA,MAAA,OAAO,QAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,OAAA,EAAmD;AAC7D,IAAA,IAAI;AACF,MAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,OAAA,EAAS;AACxB,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,MAAM,SAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,OAAO,UAAU,CAAA;AACzD,MAAA,IAAI,CAAC,QAAQ,KAAA,EAAO;AAClB,QAAA,OAAO,IAAA;AAAA,MACT;AAGA,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,KAAK,CAAA;AACjD,MAAA,MAAM,OAAA,GAAuB,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAGjD,MAAA,IAAI,OAAA,CAAQ,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI,EAAG;AAChC,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,OAAO,OAAA;AAAA,IACT,SAAS,KAAA,EAAO;AAEd,MAAA,IAAI,OAAA,CAAQ,IAAI,iBAAA,EAAmB;AACjC,QAAA,OAAA,CAAQ,IAAA,CAAK,yCAAyC,KAAK,CAAA;AAAA,MAC7D;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAA,EAAsC;AAC1C,IAAA,IAAI;AACF,MAAA,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA;AAAA,IAChD,SAAS,KAAA,EAAO;AAEd,MAAA,IAAI,OAAA,CAAQ,IAAI,iBAAA,EAAmB;AACjC,QAAA,OAAA,CAAQ,IAAA,CAAK,yCAAyC,KAAK,CAAA;AAAA,MAC7D;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QAAQ,IAAA,EAA+B;AACnD,IAAA,IAAI;AAGF,MAAA,MAAM,GAAA,GAAM,KAAK,MAAA,CAAO,aAAA;AACxB,MAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY,CAAE,OAAO,IAAI,CAAA;AAG7C,MAAA,MAAM,UAAA,GAAa,IAAI,UAAA,CAAW,OAAA,CAAQ,MAAM,CAAA;AAChD,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACvC,QAAA,UAAA,CAAW,CAAC,CAAA,GAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,IAAK,KAAK,GAAA,CAAI,UAAA,CAAW,CAAA,GAAI,GAAA,CAAI,MAAM,CAAA;AAAA,MACnE;AAGA,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,UAAA,EAAY,CAAC,IAAA,KAAS,MAAA,CAAO,YAAA,CAAa,IAAI,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC,CAAA;AAAA,IAClF,SAAS,KAAA,EAAO;AAEd,MAAA,OAAO,KAAK,IAAI,CAAA;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QAAQ,IAAA,EAA+B;AACnD,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,KAAK,MAAA,CAAO,aAAA;AACxB,MAAA,MAAM,OAAA,GAAU,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,CAAC,CAAC,CAAA;AAGlE,MAAA,MAAM,YAAA,GAAe,IAAI,UAAA,CAAW,OAAA,CAAQ,MAAM,CAAA;AAClD,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACvC,QAAA,YAAA,CAAa,CAAC,CAAA,GAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,IAAK,KAAK,GAAA,CAAI,UAAA,CAAW,CAAA,GAAI,GAAA,CAAI,MAAM,CAAA;AAAA,MACrE;AAEA,MAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,YAAY,CAAA;AAAA,IAC9C,SAAS,KAAA,EAAO;AAEd,MAAA,OAAO,KAAK,IAAI,CAAA;AAAA,IAClB;AAAA,EACF;AACF;AAMO,IAAM,0BAAN,MAA8B;AAAA,EACnC,OAAO,MAAM,OAAA,EAAqD;AAChE,IAAA,IAAI;AAEF,MAAA,MAAM,KAAA,GAAQ,QAAQ,mBAAmB,CAAA;AACzC,MAAA,MAAM,UAAA,GAAa,QAAQ,wBAAwB,CAAA;AACnD,MAAA,MAAM,SAAA,GAAY,QAAQ,gBAAgB,CAAA;AAE1C,MAAA,IAAI,KAAA,IAAS,cAAc,SAAA,EAAW;AACpC,QAAA,OAAO;AAAA,UACL,EAAA,EAAI,SAAA;AAAA,UACJ,KAAA;AAAA,UACA,UAAA,EAAY,WAAW,UAAU,CAAA;AAAA,UACjC,UAAA,EAAY,KAAK,GAAA,EAAI;AAAA,UACrB,OAAA,EAAS,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA;AAAA,SACxB;AAAA,MACF;AAGA,MAAA,MAAM,YAAA,GAAe,QAAQ,QAAQ,CAAA;AACrC,MAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,QAAA,CAAS,wBAAwB,CAAA,EAAG;AAEnE,QAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,KAAA,CAAM,+BAA+B,CAAA;AAChE,QAAA,IAAI,KAAA,IAAS,KAAA,CAAM,CAAC,CAAA,EAAG;AACrB,UAAA,IAAI;AACF,YAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAC7B,YAAA,OAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,UAC3B,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,OAAO,UAAA,CAAW,QAAA,EAAe,OAAA,EAA4B;AAC3D,IAAA,IAAI;AAEF,MAAA,IAAI,SAAS,SAAA,EAAW;AACtB,QAAA,QAAA,CAAS,SAAA,CAAU,mBAAA,EAAqB,OAAA,CAAQ,KAAK,CAAA;AACrD,QAAA,QAAA,CAAS,SAAA,CAAU,wBAAA,EAA0B,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA;AAC1E,QAAA,QAAA,CAAS,SAAA,CAAU,gBAAA,EAAkB,OAAA,CAAQ,EAAE,CAAA;AAAA,MACjD,CAAA,MAAA,IAAW,QAAA,CAAS,OAAA,IAAW,QAAA,CAAS,QAAQ,GAAA,EAAK;AACnD,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,mBAAA,EAAqB,OAAA,CAAQ,KAAK,CAAA;AACvD,QAAA,QAAA,CAAS,QAAQ,GAAA,CAAI,wBAAA,EAA0B,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA;AAC5E,QAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAA,EAAkB,OAAA,CAAQ,EAAE,CAAA;AAAA,MACnD;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF","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-shared';\nimport { shouldEnforce } from '@kya-os/agentshield-shared';\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 || process.env.AGENTSHIELD_SECRET || '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 || !shouldEnforce(result)) {\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(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(''));\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['kya-session-agent'];\n const confidence = headers['kya-session-confidence'];\n const sessionId = headers['kya-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('KYA-Session-Agent', session.agent);\n response.setHeader('KYA-Session-Confidence', session.confidence.toString());\n response.setHeader('KYA-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('kya-session-agent', session.agent);\n response.headers.set('kya-session-confidence', session.confidence.toString());\n response.headers.set('kya-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
@@ -284,7 +284,15 @@ async function verifyAgentSignature(method, path, headers) {
284
284
  }
285
285
  let agent;
286
286
  let agentKey;
287
- if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
287
+ const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
288
+ try {
289
+ const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
290
+ return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
291
+ } catch {
292
+ return false;
293
+ }
294
+ })();
295
+ if (isChatGPT) {
288
296
  agent = "ChatGPT";
289
297
  agentKey = "chatgpt";
290
298
  }