@kya-os/checkpoint-nextjs 1.0.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 (122) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/EDGE_RUNTIME_WASM_SETUP.md +348 -0
  3. package/README.md +414 -0
  4. package/bin/setup-edge-wasm.js +497 -0
  5. package/dist/.tsbuildinfo +1 -0
  6. package/dist/adapt.d.mts +39 -0
  7. package/dist/adapt.d.ts +39 -0
  8. package/dist/adapt.js +58 -0
  9. package/dist/adapt.js.map +1 -0
  10. package/dist/adapt.mjs +56 -0
  11. package/dist/adapt.mjs.map +1 -0
  12. package/dist/api-client.d.mts +204 -0
  13. package/dist/api-client.d.ts +204 -0
  14. package/dist/api-client.js +206 -0
  15. package/dist/api-client.js.map +1 -0
  16. package/dist/api-client.mjs +199 -0
  17. package/dist/api-client.mjs.map +1 -0
  18. package/dist/api-middleware.d.mts +156 -0
  19. package/dist/api-middleware.d.ts +156 -0
  20. package/dist/api-middleware.js +510 -0
  21. package/dist/api-middleware.js.map +1 -0
  22. package/dist/api-middleware.mjs +505 -0
  23. package/dist/api-middleware.mjs.map +1 -0
  24. package/dist/create-middleware.d.mts +17 -0
  25. package/dist/create-middleware.d.ts +17 -0
  26. package/dist/create-middleware.js +38 -0
  27. package/dist/create-middleware.js.map +1 -0
  28. package/dist/create-middleware.mjs +35 -0
  29. package/dist/create-middleware.mjs.map +1 -0
  30. package/dist/edge/index.d.mts +110 -0
  31. package/dist/edge/index.d.ts +110 -0
  32. package/dist/edge/index.js +277 -0
  33. package/dist/edge/index.js.map +1 -0
  34. package/dist/edge/index.mjs +275 -0
  35. package/dist/edge/index.mjs.map +1 -0
  36. package/dist/edge-runtime-loader.d.mts +50 -0
  37. package/dist/edge-runtime-loader.d.ts +50 -0
  38. package/dist/edge-runtime-loader.js +204 -0
  39. package/dist/edge-runtime-loader.js.map +1 -0
  40. package/dist/edge-runtime-loader.mjs +201 -0
  41. package/dist/edge-runtime-loader.mjs.map +1 -0
  42. package/dist/edge-wasm-middleware.d.mts +68 -0
  43. package/dist/edge-wasm-middleware.d.ts +68 -0
  44. package/dist/edge-wasm-middleware.js +318 -0
  45. package/dist/edge-wasm-middleware.js.map +1 -0
  46. package/dist/edge-wasm-middleware.mjs +315 -0
  47. package/dist/edge-wasm-middleware.mjs.map +1 -0
  48. package/dist/index.d.mts +25 -0
  49. package/dist/index.d.ts +25 -0
  50. package/dist/index.js +1019 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/index.mjs +979 -0
  53. package/dist/index.mjs.map +1 -0
  54. package/dist/middleware-edge.d.mts +46 -0
  55. package/dist/middleware-edge.d.ts +46 -0
  56. package/dist/middleware-edge.js +134 -0
  57. package/dist/middleware-edge.js.map +1 -0
  58. package/dist/middleware-edge.mjs +129 -0
  59. package/dist/middleware-edge.mjs.map +1 -0
  60. package/dist/middleware-node.d.mts +89 -0
  61. package/dist/middleware-node.d.ts +89 -0
  62. package/dist/middleware-node.js +127 -0
  63. package/dist/middleware-node.js.map +1 -0
  64. package/dist/middleware-node.mjs +124 -0
  65. package/dist/middleware-node.mjs.map +1 -0
  66. package/dist/middleware.d.mts +36 -0
  67. package/dist/middleware.d.ts +36 -0
  68. package/dist/middleware.js +15 -0
  69. package/dist/middleware.js.map +1 -0
  70. package/dist/middleware.mjs +12 -0
  71. package/dist/middleware.mjs.map +1 -0
  72. package/dist/nodejs-wasm-loader.d.mts +25 -0
  73. package/dist/nodejs-wasm-loader.d.ts +25 -0
  74. package/dist/nodejs-wasm-loader.js +95 -0
  75. package/dist/nodejs-wasm-loader.js.map +1 -0
  76. package/dist/nodejs-wasm-loader.mjs +85 -0
  77. package/dist/nodejs-wasm-loader.mjs.map +1 -0
  78. package/dist/policy.d.mts +162 -0
  79. package/dist/policy.d.ts +162 -0
  80. package/dist/policy.js +189 -0
  81. package/dist/policy.js.map +1 -0
  82. package/dist/policy.mjs +165 -0
  83. package/dist/policy.mjs.map +1 -0
  84. package/dist/session-tracker.d.mts +55 -0
  85. package/dist/session-tracker.d.ts +55 -0
  86. package/dist/session-tracker.js +170 -0
  87. package/dist/session-tracker.js.map +1 -0
  88. package/dist/session-tracker.mjs +167 -0
  89. package/dist/session-tracker.mjs.map +1 -0
  90. package/dist/signature-verifier.d.mts +33 -0
  91. package/dist/signature-verifier.d.ts +33 -0
  92. package/dist/signature-verifier.js +386 -0
  93. package/dist/signature-verifier.js.map +1 -0
  94. package/dist/signature-verifier.mjs +362 -0
  95. package/dist/signature-verifier.mjs.map +1 -0
  96. package/dist/translate.d.mts +33 -0
  97. package/dist/translate.d.ts +33 -0
  98. package/dist/translate.js +38 -0
  99. package/dist/translate.js.map +1 -0
  100. package/dist/translate.mjs +36 -0
  101. package/dist/translate.mjs.map +1 -0
  102. package/dist/types-C-xCUNTr.d.mts +105 -0
  103. package/dist/types-C-xCUNTr.d.ts +105 -0
  104. package/dist/wasm-middleware.d.mts +63 -0
  105. package/dist/wasm-middleware.d.ts +63 -0
  106. package/dist/wasm-middleware.js +98 -0
  107. package/dist/wasm-middleware.js.map +1 -0
  108. package/dist/wasm-middleware.mjs +95 -0
  109. package/dist/wasm-middleware.mjs.map +1 -0
  110. package/dist/wasm-setup.d.mts +46 -0
  111. package/dist/wasm-setup.d.ts +46 -0
  112. package/dist/wasm-setup.js +176 -0
  113. package/dist/wasm-setup.js.map +1 -0
  114. package/dist/wasm-setup.mjs +167 -0
  115. package/dist/wasm-setup.mjs.map +1 -0
  116. package/package.json +156 -0
  117. package/templates/middleware-wasm-100.ts +153 -0
  118. package/wasm/agentshield_wasm.d.ts +479 -0
  119. package/wasm/agentshield_wasm.js +1536 -0
  120. package/wasm/agentshield_wasm_bg.wasm +0 -0
  121. package/wasm/package.json +30 -0
  122. package/wasm.d.ts +21 -0
@@ -0,0 +1,156 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { EnforcementDecision } from './api-client.js';
3
+ import '@kya-os/checkpoint-shared';
4
+
5
+ /**
6
+ * API-based AgentShield Middleware for Next.js
7
+ *
8
+ * This middleware uses the AgentShield API for detection and enforcement,
9
+ * instead of running detection locally. This approach:
10
+ *
11
+ * 1. Works reliably in Edge Runtime (no WASM loading issues)
12
+ * 2. Ensures consistent detection across all platforms
13
+ * 3. Applies centralized policies from the dashboard
14
+ * 4. Supports deny lists, thresholds, and path rules
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // middleware.ts
19
+ * import { withCheckpointApi } from '@kya-os/checkpoint-nextjs/api-middleware';
20
+ *
21
+ * export default withCheckpointApi({
22
+ * apiKey: process.env.CHECKPOINT_API_KEY!,
23
+ * // Optional overrides:
24
+ * onBlock: 'redirect', // 'block' | 'redirect' | 'challenge'
25
+ * redirectUrl: '/blocked',
26
+ * skipPaths: ['/api/health', '/_next/*'],
27
+ * });
28
+ *
29
+ * export const config = {
30
+ * matcher: ['/((?!_next/static|favicon.ico).*)'],
31
+ * };
32
+ * ```
33
+ */
34
+
35
+ /**
36
+ * Middleware configuration
37
+ */
38
+ interface CheckpointApiMiddlewareConfig {
39
+ /** API key (or use CHECKPOINT_API_KEY env var) */
40
+ apiKey?: string;
41
+ /** API base URL (defaults to production) */
42
+ apiUrl?: string;
43
+ /**
44
+ * Use edge detection for lower latency (~30-50ms vs ~150ms) and better coverage.
45
+ * Edge detection can identify non-JS clients (curl, Python, Claude Code WebFetch)
46
+ * that the pixel cannot detect since they don't execute JavaScript.
47
+ * Set to false to use the Vercel API instead.
48
+ * @default true
49
+ */
50
+ useEdge?: boolean;
51
+ /** Request timeout in ms (default: 5000) */
52
+ timeout?: number;
53
+ /**
54
+ * Action to take when an agent should be blocked
55
+ * - 'block': Return 403 response
56
+ * - 'redirect': Redirect to redirectUrl
57
+ * - 'challenge': Show a challenge page (future)
58
+ * Default: uses policy from dashboard
59
+ */
60
+ onBlock?: 'block' | 'redirect' | 'challenge';
61
+ /**
62
+ * URL to redirect to when blocking (if onBlock is 'redirect')
63
+ * Default: uses redirectUrl from dashboard policy
64
+ */
65
+ redirectUrl?: string;
66
+ /**
67
+ * How the middleware handles a `redirect` / `instruct` action.
68
+ *
69
+ * - `'instruct'` (default): return HTTP 401 with an MCP-I Link header + JSON
70
+ * body pointing the agent at the redirect URL. LLMs surface the URL as a
71
+ * clickable link for the user. Matches the Cloudflare Gateway contract.
72
+ * - `'http'`: legacy behavior — return HTTP 302 with `Location`. Most LLM
73
+ * fetchers won't follow the redirect, so this is only useful when your
74
+ * traffic is real browsers.
75
+ *
76
+ * @default 'instruct'
77
+ */
78
+ redirectMode?: 'instruct' | 'http';
79
+ /**
80
+ * Custom blocked response
81
+ */
82
+ blockedResponse?: {
83
+ status?: number;
84
+ message?: string;
85
+ headers?: Record<string, string>;
86
+ };
87
+ /**
88
+ * Paths to skip (in addition to dashboard policy)
89
+ * Supports glob patterns: '/api/*', '/_next/*'
90
+ */
91
+ skipPaths?: string[];
92
+ /**
93
+ * Only enforce on these paths (overrides dashboard policy)
94
+ */
95
+ includePaths?: string[];
96
+ /**
97
+ * Callback when an agent is detected
98
+ */
99
+ onAgentDetected?: (request: NextRequest, decision: EnforcementDecision) => void | Promise<void>;
100
+ /**
101
+ * Callback to customize the blocked response
102
+ */
103
+ customBlockedResponse?: (request: NextRequest, decision: EnforcementDecision) => NextResponse | Promise<NextResponse>;
104
+ /**
105
+ * Whether to fail open (allow) on API errors
106
+ * Default: true (recommended for production)
107
+ */
108
+ failOpen?: boolean;
109
+ /**
110
+ * Enable debug logging
111
+ */
112
+ debug?: boolean;
113
+ }
114
+ /**
115
+ * Create AgentShield middleware with API-based detection
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // middleware.ts
120
+ * import { withCheckpointApi } from '@kya-os/checkpoint-nextjs/api-middleware';
121
+ *
122
+ * export default withCheckpointApi({
123
+ * onBlock: 'block',
124
+ * skipPaths: ['/api/health'],
125
+ * });
126
+ * ```
127
+ */
128
+ declare function withCheckpointApi(config?: CheckpointApiMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
129
+ /** @deprecated Renamed to {@link withCheckpointApi}. The behaviour is identical. */
130
+ declare const withAgentShield: typeof withCheckpointApi;
131
+ /** @deprecated Renamed to {@link CheckpointApiMiddlewareConfig}. */
132
+ type AgentShieldMiddlewareConfig = CheckpointApiMiddlewareConfig;
133
+ /**
134
+ * @deprecated Module-load-time invocation of the legacy `withAgentShield()`.
135
+ * Construct the middleware explicitly via `withCheckpointApi({ apiKey })`
136
+ * instead of relying on a default-constructed singleton at import time.
137
+ */
138
+ declare function agentShieldMiddleware(_request: NextRequest): Promise<NextResponse>;
139
+ /**
140
+ * @deprecated The "enhanced middleware" combined detection + storage +
141
+ * enforcement into a single legacy path that no longer exists. Migrate
142
+ * to `withCheckpoint` (local-engine) or `withCheckpointApi` (SaaS-gateway).
143
+ */
144
+ declare function createEnhancedAgentShieldMiddleware(_config?: EnhancedMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
145
+ /** @deprecated The enhanced-middleware path is gone. Use `CheckpointConfig` (local-engine) or `CheckpointApiMiddlewareConfig` (SaaS). */
146
+ type EnhancedMiddlewareConfig = Record<string, unknown>;
147
+ /** @deprecated Storage was tied to the legacy enhanced-middleware path. */
148
+ type StorageAdapter = Record<string, unknown>;
149
+ /** @deprecated Storage was tied to the legacy enhanced-middleware path. */
150
+ type StorageConfig = Record<string, unknown>;
151
+ /** @deprecated Detection events now flow through the engine; the legacy event shape no longer applies. */
152
+ type AgentDetectionEvent = Record<string, unknown>;
153
+ /** @deprecated Use `EdgeSessionTracker` / `StatelessSessionChecker` from `./session-tracker`. */
154
+ type AgentSession = Record<string, unknown>;
155
+
156
+ export { type AgentDetectionEvent, type AgentSession, type AgentShieldMiddlewareConfig, type CheckpointApiMiddlewareConfig, type EnhancedMiddlewareConfig, type StorageAdapter, type StorageConfig, agentShieldMiddleware, createEnhancedAgentShieldMiddleware, withAgentShield, withCheckpointApi };
@@ -0,0 +1,510 @@
1
+ 'use strict';
2
+
3
+ var server = require('next/server');
4
+ var checkpointShared = require('@kya-os/checkpoint-shared');
5
+
6
+ // src/api-middleware.ts
7
+
8
+ // src/api-client.ts
9
+ var DEFAULT_BASE_URL = "https://kya.vouched.id";
10
+ var EDGE_DETECT_URL = "https://detect.checkpoint-gateway.ai";
11
+ var DEFAULT_TIMEOUT = 5e3;
12
+ var CheckpointApiClient = class {
13
+ apiKey;
14
+ baseUrl;
15
+ useEdge;
16
+ timeout;
17
+ debug;
18
+ constructor(config) {
19
+ if (!config.apiKey) {
20
+ throw new Error("AgentShield API key is required");
21
+ }
22
+ this.apiKey = config.apiKey;
23
+ this.useEdge = config.useEdge !== false;
24
+ this.baseUrl = config.baseUrl || (this.useEdge ? EDGE_DETECT_URL : DEFAULT_BASE_URL);
25
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
26
+ this.debug = config.debug || false;
27
+ }
28
+ /**
29
+ * Call the enforce API to check if a request should be allowed
30
+ */
31
+ async enforce(input) {
32
+ const startTime = Date.now();
33
+ try {
34
+ const controller = new AbortController();
35
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
36
+ try {
37
+ const endpoint = this.useEdge ? `${this.baseUrl}/__detect/enforce` : `${this.baseUrl}/api/v1/enforce`;
38
+ const response = await fetch(endpoint, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "application/json",
42
+ Authorization: `Bearer ${this.apiKey}`,
43
+ "X-Request-ID": input.requestId || crypto.randomUUID()
44
+ },
45
+ body: JSON.stringify(input),
46
+ signal: controller.signal
47
+ });
48
+ clearTimeout(timeoutId);
49
+ const data = await response.json();
50
+ if (this.debug) {
51
+ console.log("[AgentShield] Enforce response:", {
52
+ status: response.status,
53
+ action: data.data?.decision.action,
54
+ processingTimeMs: Date.now() - startTime
55
+ });
56
+ }
57
+ if (!response.ok) {
58
+ return {
59
+ success: false,
60
+ error: {
61
+ code: `HTTP_${response.status}`,
62
+ message: data.error?.message || `HTTP error: ${response.status}`
63
+ }
64
+ };
65
+ }
66
+ return data;
67
+ } catch (error) {
68
+ clearTimeout(timeoutId);
69
+ throw error;
70
+ }
71
+ } catch (error) {
72
+ if (error instanceof Error && error.name === "AbortError") {
73
+ if (this.debug) {
74
+ console.warn("[AgentShield] Request timed out");
75
+ }
76
+ return {
77
+ success: false,
78
+ error: {
79
+ code: "TIMEOUT",
80
+ message: `Request timed out after ${this.timeout}ms`
81
+ }
82
+ };
83
+ }
84
+ if (this.debug) {
85
+ console.error("[AgentShield] Request failed:", error);
86
+ }
87
+ return {
88
+ success: false,
89
+ error: {
90
+ code: "NETWORK_ERROR",
91
+ message: error instanceof Error ? error.message : "Network request failed"
92
+ }
93
+ };
94
+ }
95
+ }
96
+ /**
97
+ * Quick check - returns just the action without full response parsing
98
+ * Useful for very fast middleware that just needs allow/block
99
+ */
100
+ async quickCheck(input) {
101
+ const result = await this.enforce(input);
102
+ if (!result.success || !result.data) {
103
+ return {
104
+ action: "allow",
105
+ error: result.error?.message
106
+ };
107
+ }
108
+ return {
109
+ action: result.data.decision.action
110
+ };
111
+ }
112
+ /**
113
+ * Check if this client is using edge detection (Gateway Worker)
114
+ */
115
+ isUsingEdge() {
116
+ return this.useEdge;
117
+ }
118
+ /**
119
+ * Log a detection result to AgentShield database.
120
+ * Use after Gateway Worker detection to persist results.
121
+ * Fire-and-forget - returns immediately without waiting for DB write.
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // After receiving Gateway response
126
+ * if (client.isUsingEdge() && response.data?.detection) {
127
+ * client.logDetection({
128
+ * detection: response.data.detection,
129
+ * context: { userAgent, ipAddress, path, url, method }
130
+ * }).catch(err => console.error('Log failed:', err));
131
+ * }
132
+ * ```
133
+ */
134
+ async logDetection(input) {
135
+ const logEndpoint = this.useEdge ? `${DEFAULT_BASE_URL}/api/v1/log-detection` : `${this.baseUrl}/api/v1/log-detection`;
136
+ try {
137
+ const controller = new AbortController();
138
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
139
+ try {
140
+ const response = await fetch(logEndpoint, {
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ Authorization: `Bearer ${this.apiKey}`
145
+ },
146
+ body: JSON.stringify({
147
+ detection: {
148
+ isAgent: input.detection.isAgent,
149
+ confidence: input.detection.confidence,
150
+ agentName: input.detection.agentName,
151
+ agentType: input.detection.agentType,
152
+ detectionClass: input.detection.detectionClass,
153
+ verificationMethod: input.detection.verificationMethod,
154
+ reasons: input.detection.reasons
155
+ },
156
+ context: input.context,
157
+ source: input.source || "gateway"
158
+ }),
159
+ signal: controller.signal
160
+ });
161
+ clearTimeout(timeoutId);
162
+ if (!response.ok && this.debug) {
163
+ console.warn("[AgentShield] Log detection returned non-2xx:", response.status);
164
+ }
165
+ } catch (error) {
166
+ clearTimeout(timeoutId);
167
+ throw error;
168
+ }
169
+ } catch (error) {
170
+ if (this.debug) {
171
+ console.error("[AgentShield] Log detection failed:", error);
172
+ }
173
+ throw error;
174
+ }
175
+ }
176
+ };
177
+ var clientInstance = null;
178
+ function getCheckpointApiClient(config) {
179
+ if (!clientInstance) {
180
+ const apiKey = config?.apiKey || process.env.CHECKPOINT_API_KEY;
181
+ if (!apiKey) {
182
+ throw new Error(
183
+ "AgentShield API key is required. Set CHECKPOINT_API_KEY environment variable or pass apiKey in config."
184
+ );
185
+ }
186
+ clientInstance = new CheckpointApiClient({
187
+ apiKey,
188
+ baseUrl: config?.baseUrl || process.env.AGENTSHIELD_API_URL,
189
+ // Default to edge detection unless explicitly disabled
190
+ useEdge: config?.useEdge ?? process.env.AGENTSHIELD_USE_EDGE !== "false",
191
+ timeout: config?.timeout,
192
+ debug: config?.debug || process.env.AGENTSHIELD_DEBUG === "true"
193
+ });
194
+ }
195
+ return clientInstance;
196
+ }
197
+
198
+ // src/utils.ts
199
+ function getClientIp(request) {
200
+ const forwardedFor = request.headers.get("x-forwarded-for");
201
+ if (forwardedFor) {
202
+ const ip = forwardedFor.split(",")[0]?.trim();
203
+ if (ip) return ip;
204
+ }
205
+ const realIp = request.headers.get("x-real-ip");
206
+ if (realIp) return realIp;
207
+ const cfIp = request.headers.get("cf-connecting-ip");
208
+ if (cfIp) return cfIp;
209
+ const clientIp = request.headers.get("x-client-ip");
210
+ if (clientIp) return clientIp;
211
+ return void 0;
212
+ }
213
+ function safeHostname(url) {
214
+ try {
215
+ return new URL(url).hostname;
216
+ } catch {
217
+ return "this site";
218
+ }
219
+ }
220
+
221
+ // src/responses/agent-instruction.ts
222
+ var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
223
+ var DEFAULT_CONNECT_PATH = "/connect";
224
+ function buildAgentInstructionResponse(request, decision, redirectUrl) {
225
+ const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
226
+ const agentName = decision.agentName || decision.agentType || "unknown";
227
+ if (!resolved.searchParams.has("agent")) {
228
+ resolved.searchParams.set("agent", agentName.toLowerCase());
229
+ }
230
+ const authUrl = resolved.toString();
231
+ const hostname = safeHostname(request.url);
232
+ const body = {
233
+ // Markdown-formatted so clients that render markdown (Claude Desktop,
234
+ // ChatGPT web) surface the URL as a clickable link. Tone mirrors the
235
+ // gateway response so messaging stays consistent across platforms.
236
+ message: `I can't access ${hostname} yet \u2014 this site checks AI assistants at the front door.
237
+
238
+ **To give me access, open this link:**
239
+ [Connect securely to ${hostname}](${authUrl})
240
+
241
+ It only takes a moment and you won't need to do it again. Once you're done, ask me to try again and I'll connect through the verified channel automatically.`,
242
+ user_action_required: {
243
+ action: `Connect securely to ${hostname}`,
244
+ url: authUrl,
245
+ reason: `${hostname} checks AI assistants before they connect. Open the link to give your assistant a verified key.`
246
+ },
247
+ mcp_i: {
248
+ version: "1.0",
249
+ action: "authenticate",
250
+ authorization_url: authUrl,
251
+ flow: {
252
+ type: "oauth2_delegation",
253
+ steps: [
254
+ "1. Direct your user to the authorization_url",
255
+ "2. User reviews requested scopes and grants consent",
256
+ "3. Receive delegation credential (JWT)",
257
+ "4. Include credential in KYA-Delegation header",
258
+ "5. Retry this request with the proof"
259
+ ]
260
+ },
261
+ retry_instructions: {
262
+ header: "KYA-Delegation",
263
+ format: "JWT delegation credential from authorization flow"
264
+ },
265
+ documentation: MCP_I_DOCS_URL
266
+ },
267
+ error: "mcp_authentication_required",
268
+ code: "AGENT_REQUIRES_DELEGATION",
269
+ detection: {
270
+ agent_type: decision.agentType || "ai_agent",
271
+ agent_name: decision.agentName || "Unknown Agent",
272
+ confidence: decision.confidence
273
+ }
274
+ };
275
+ const response = server.NextResponse.json(body, { status: 401 });
276
+ response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
277
+ response.headers.set(
278
+ "Link",
279
+ `<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
280
+ );
281
+ response.headers.set("KYA-Auth-Required", "true");
282
+ response.headers.set("KYA-Auth-Url", authUrl);
283
+ response.headers.set("KYA-Action", "instruct");
284
+ response.headers.set("KYA-Detected-Agent", agentName);
285
+ response.headers.set("KYA-Confidence", decision.confidence.toString());
286
+ response.headers.set("Cache-Control", "no-store");
287
+ return response;
288
+ }
289
+ function resolveUrl(target, baseUrl) {
290
+ try {
291
+ return new URL(target, baseUrl);
292
+ } catch {
293
+ return new URL(DEFAULT_CONNECT_PATH, baseUrl);
294
+ }
295
+ }
296
+
297
+ // src/api-middleware.ts
298
+ function shouldSkipPath(path, skipPaths) {
299
+ return skipPaths.some((pattern) => checkpointShared.matchPath(path, pattern));
300
+ }
301
+ function shouldIncludePath(path, includePaths) {
302
+ if (!includePaths || includePaths.length === 0) return true;
303
+ return includePaths.some((pattern) => checkpointShared.matchPath(path, pattern));
304
+ }
305
+ function buildBlockedResponse(request, decision, config) {
306
+ const status = config.blockedResponse?.status ?? 403;
307
+ const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
308
+ const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
309
+ const body = {
310
+ error: message,
311
+ code: "AGENT_BLOCKED",
312
+ reason: decision.reason,
313
+ agentType: decision.agentType
314
+ };
315
+ if (recoveryUrl) {
316
+ const hostname = safeHostname(request.url);
317
+ body.user_action_required = {
318
+ action: `Connect securely to ${hostname}`,
319
+ url: recoveryUrl,
320
+ reason: `${hostname} blocks unverified AI assistants. Open the link to give your assistant a verified key and try again.`
321
+ };
322
+ body.message = `I can't access ${hostname} \u2014 this site blocks unverified AI assistants.
323
+
324
+ **To give me access, open this link:**
325
+ [Connect securely to ${hostname}](${recoveryUrl})
326
+
327
+ Once you're done, ask me to try again.`;
328
+ }
329
+ const response = server.NextResponse.json(body, { status });
330
+ if (config.blockedResponse?.headers) {
331
+ for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
332
+ response.headers.set(key, value);
333
+ }
334
+ }
335
+ if (recoveryUrl) {
336
+ response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
337
+ response.headers.set("KYA-Auth-Url", recoveryUrl);
338
+ }
339
+ response.headers.set("KYA-Action", decision.action);
340
+ response.headers.set("KYA-Reason", decision.reason);
341
+ return response;
342
+ }
343
+ function resolveRecoveryUrl(request, target) {
344
+ if (!target) return void 0;
345
+ try {
346
+ return new URL(target, request.url).toString();
347
+ } catch {
348
+ return void 0;
349
+ }
350
+ }
351
+ function buildRedirectResponse(request, decision, config) {
352
+ const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
353
+ const url = new URL(redirectUrl, request.url);
354
+ url.searchParams.set("reason", decision.reason);
355
+ if (decision.agentType) {
356
+ url.searchParams.set("agent", decision.agentType);
357
+ }
358
+ return server.NextResponse.redirect(url);
359
+ }
360
+ function withCheckpointApi(config = {}) {
361
+ let client = null;
362
+ const getClient = () => {
363
+ if (!client) {
364
+ client = getCheckpointApiClient({
365
+ apiKey: config.apiKey,
366
+ baseUrl: config.apiUrl,
367
+ useEdge: config.useEdge,
368
+ timeout: config.timeout,
369
+ debug: config.debug
370
+ });
371
+ }
372
+ return client;
373
+ };
374
+ const defaultSkipPaths = [
375
+ "/_next/static/**",
376
+ "/_next/image/**",
377
+ "/favicon.ico",
378
+ "/robots.txt",
379
+ "/sitemap.xml"
380
+ ];
381
+ const skipPaths = [...defaultSkipPaths, ...config.skipPaths || []];
382
+ const failOpen = config.failOpen ?? true;
383
+ return async function middleware(request) {
384
+ const path = request.nextUrl.pathname;
385
+ const startTime = Date.now();
386
+ if (shouldSkipPath(path, skipPaths)) {
387
+ return server.NextResponse.next();
388
+ }
389
+ if (!shouldIncludePath(path, config.includePaths)) {
390
+ return server.NextResponse.next();
391
+ }
392
+ try {
393
+ const client2 = getClient();
394
+ const userAgent = request.headers.get("user-agent") || void 0;
395
+ const ipAddress = getClientIp(request);
396
+ const result = await client2.enforce({
397
+ headers: Object.fromEntries(request.headers.entries()),
398
+ userAgent,
399
+ ipAddress,
400
+ path,
401
+ url: request.url,
402
+ method: request.method,
403
+ requestId: request.headers.get("x-request-id") || void 0,
404
+ options: {
405
+ // Always include detection results for logging (needed when using edge)
406
+ includeDetectionResult: true
407
+ }
408
+ });
409
+ if (!result.success || !result.data) {
410
+ if (config.debug) {
411
+ console.warn("[AgentShield] API error:", result.error);
412
+ }
413
+ if (failOpen) {
414
+ return server.NextResponse.next();
415
+ }
416
+ return server.NextResponse.json(
417
+ { error: "Security check failed", code: "API_ERROR" },
418
+ { status: 503 }
419
+ );
420
+ }
421
+ const decision = result.data.decision;
422
+ if (config.debug) {
423
+ console.log("[AgentShield] Decision:", {
424
+ path,
425
+ action: decision.action,
426
+ isAgent: decision.isAgent,
427
+ confidence: decision.confidence,
428
+ agentName: decision.agentName,
429
+ detectionMethod: result.data.detection?.detectionMethod || "not-included",
430
+ processingTimeMs: Date.now() - startTime
431
+ });
432
+ }
433
+ if (client2.isUsingEdge() && result.data.detection) {
434
+ client2.logDetection({
435
+ detection: result.data.detection,
436
+ context: { userAgent, ipAddress, path, url: request.url, method: request.method }
437
+ }).catch((err) => {
438
+ if (config.debug) {
439
+ console.error("[AgentShield] Log detection failed:", err);
440
+ }
441
+ });
442
+ }
443
+ if (decision.isAgent && config.onAgentDetected) {
444
+ await config.onAgentDetected(request, decision);
445
+ }
446
+ const redirectMode = config.redirectMode ?? "instruct";
447
+ switch (decision.action) {
448
+ case "block": {
449
+ if (config.customBlockedResponse) {
450
+ return await config.customBlockedResponse(request, decision);
451
+ }
452
+ if (config.onBlock === "redirect") {
453
+ return buildRedirectResponse(request, decision, config);
454
+ }
455
+ return buildBlockedResponse(request, decision, config);
456
+ }
457
+ case "redirect":
458
+ case "instruct": {
459
+ if (redirectMode === "http" && decision.action === "redirect") {
460
+ return buildRedirectResponse(request, decision, config);
461
+ }
462
+ const targetUrl = config.redirectUrl || decision.redirectUrl;
463
+ return buildAgentInstructionResponse(request, decision, targetUrl);
464
+ }
465
+ case "challenge": {
466
+ return buildRedirectResponse(request, decision, config);
467
+ }
468
+ case "log":
469
+ case "allow":
470
+ default: {
471
+ const response = server.NextResponse.next();
472
+ if (decision.isAgent) {
473
+ response.headers.set("KYA-Detected", "true");
474
+ response.headers.set("KYA-Confidence", decision.confidence.toString());
475
+ if (decision.agentName) {
476
+ response.headers.set("KYA-Agent", decision.agentName);
477
+ }
478
+ }
479
+ return response;
480
+ }
481
+ }
482
+ } catch (error) {
483
+ if (config.debug) {
484
+ console.error("[AgentShield] Middleware error:", error);
485
+ }
486
+ if (failOpen) {
487
+ return server.NextResponse.next();
488
+ }
489
+ return server.NextResponse.json(
490
+ { error: "Security check failed", code: "MIDDLEWARE_ERROR" },
491
+ { status: 503 }
492
+ );
493
+ }
494
+ };
495
+ }
496
+ var withAgentShield = withCheckpointApi;
497
+ var LEGACY_MIGRATION_ERROR = "This export was removed in Phase D. Migrate to `withCheckpoint` (local-engine deployment) or `withCheckpointApi` (SaaS-gateway deployment) from `@kya-os/checkpoint-nextjs`. See packages/checkpoint-nextjs/README.md (Two deployment shapes) for which one fits your runtime.";
498
+ function agentShieldMiddleware(_request) {
499
+ throw new Error(LEGACY_MIGRATION_ERROR);
500
+ }
501
+ function createEnhancedAgentShieldMiddleware(_config = {}) {
502
+ throw new Error(LEGACY_MIGRATION_ERROR);
503
+ }
504
+
505
+ exports.agentShieldMiddleware = agentShieldMiddleware;
506
+ exports.createEnhancedAgentShieldMiddleware = createEnhancedAgentShieldMiddleware;
507
+ exports.withAgentShield = withAgentShield;
508
+ exports.withCheckpointApi = withCheckpointApi;
509
+ //# sourceMappingURL=api-middleware.js.map
510
+ //# sourceMappingURL=api-middleware.js.map