@kya-os/agentshield-nextjs 0.2.13 → 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 (72) hide show
  1. package/dist/api-client.d.mts +6 -1
  2. package/dist/api-client.d.ts +6 -1
  3. package/dist/api-client.js.map +1 -1
  4. package/dist/api-client.mjs.map +1 -1
  5. package/dist/api-middleware.d.mts +13 -0
  6. package/dist/api-middleware.d.ts +13 -0
  7. package/dist/api-middleware.js +149 -19
  8. package/dist/api-middleware.js.map +1 -1
  9. package/dist/api-middleware.mjs +149 -19
  10. package/dist/api-middleware.mjs.map +1 -1
  11. package/dist/create-middleware.js +565 -487
  12. package/dist/create-middleware.js.map +1 -1
  13. package/dist/create-middleware.mjs +565 -487
  14. package/dist/create-middleware.mjs.map +1 -1
  15. package/dist/edge/index.js +69 -46
  16. package/dist/edge/index.js.map +1 -1
  17. package/dist/edge/index.mjs +69 -46
  18. package/dist/edge/index.mjs.map +1 -1
  19. package/dist/edge-detector-wrapper.js +9 -1
  20. package/dist/edge-detector-wrapper.js.map +1 -1
  21. package/dist/edge-detector-wrapper.mjs +9 -1
  22. package/dist/edge-detector-wrapper.mjs.map +1 -1
  23. package/dist/edge-runtime-loader.d.mts +1 -0
  24. package/dist/edge-runtime-loader.d.ts +1 -0
  25. package/dist/edge-runtime-loader.js +19 -3
  26. package/dist/edge-runtime-loader.js.map +1 -1
  27. package/dist/edge-runtime-loader.mjs +19 -3
  28. package/dist/edge-runtime-loader.mjs.map +1 -1
  29. package/dist/edge-wasm-middleware.d.mts +1 -0
  30. package/dist/edge-wasm-middleware.d.ts +1 -0
  31. package/dist/edge-wasm-middleware.js +10 -2
  32. package/dist/edge-wasm-middleware.js.map +1 -1
  33. package/dist/edge-wasm-middleware.mjs +11 -3
  34. package/dist/edge-wasm-middleware.mjs.map +1 -1
  35. package/dist/enhanced-middleware.js +48 -20
  36. package/dist/enhanced-middleware.js.map +1 -1
  37. package/dist/enhanced-middleware.mjs +49 -21
  38. package/dist/enhanced-middleware.mjs.map +1 -1
  39. package/dist/index.d.mts +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +263 -102
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +264 -103
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/middleware.js +565 -487
  46. package/dist/middleware.js.map +1 -1
  47. package/dist/middleware.mjs +565 -487
  48. package/dist/middleware.mjs.map +1 -1
  49. package/dist/policy.d.mts +16 -4
  50. package/dist/policy.d.ts +16 -4
  51. package/dist/policy.js +14 -10
  52. package/dist/policy.js.map +1 -1
  53. package/dist/policy.mjs +14 -10
  54. package/dist/policy.mjs.map +1 -1
  55. package/dist/session-tracker.js +13 -19
  56. package/dist/session-tracker.js.map +1 -1
  57. package/dist/session-tracker.mjs +13 -19
  58. package/dist/session-tracker.mjs.map +1 -1
  59. package/dist/signature-verifier.js +9 -1
  60. package/dist/signature-verifier.js.map +1 -1
  61. package/dist/signature-verifier.mjs +9 -1
  62. package/dist/signature-verifier.mjs.map +1 -1
  63. package/dist/wasm-middleware.d.mts +1 -0
  64. package/dist/wasm-middleware.d.ts +1 -0
  65. package/dist/wasm-middleware.js +15 -15
  66. package/dist/wasm-middleware.js.map +1 -1
  67. package/dist/wasm-middleware.mjs +15 -15
  68. package/dist/wasm-middleware.mjs.map +1 -1
  69. package/package.json +23 -22
  70. package/wasm/agentshield_wasm.d.ts +379 -21
  71. package/wasm/agentshield_wasm.js +1409 -506
  72. package/wasm/agentshield_wasm_bg.wasm +0 -0
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { loadRulesSync, mapVerificationMethod, createEvaluationContext, evaluatePolicy, ENFORCEMENT_ACTIONS, PolicyConfigSchema, DEFAULT_POLICY, matchPath as matchPath$1, createPolicyFetcher } from '@kya-os/agentshield-shared';
1
+ import { loadRulesSync, shouldEnforce, evaluateEnforcement, createEvaluationContext, evaluatePolicy, ENFORCEMENT_ACTIONS, PolicyConfigSchema, DEFAULT_POLICY, matchPath as matchPath$1, createPolicyFetcher, mapVerificationMethod } from '@kya-os/agentshield-shared';
2
2
  export { DEFAULT_POLICY, ENFORCEMENT_ACTIONS, createEvaluationContext, evaluatePolicy } from '@kya-os/agentshield-shared';
3
3
  import { NextResponse } from 'next/server';
4
4
  import * as ed25519 from '@noble/ed25519';
@@ -232,7 +232,15 @@ var init_edge_detector_with_wasm = __esm({
232
232
  }
233
233
  const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
234
234
  const signatureAgent = normalizedHeaders["signature-agent"];
235
- if (signatureAgent?.includes("chatgpt.com")) {
235
+ const isChatGPT = (() => {
236
+ try {
237
+ const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
238
+ return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
239
+ } catch {
240
+ return false;
241
+ }
242
+ })();
243
+ if (isChatGPT) {
236
244
  confidence = 85;
237
245
  reasons.push("signature_agent:chatgpt");
238
246
  detectedAgent = { type: "chatgpt", name: "ChatGPT" };
@@ -759,7 +767,15 @@ async function verifyAgentSignature(method, path, headers) {
759
767
  }
760
768
  let agent;
761
769
  let agentKey;
762
- if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
770
+ const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
771
+ try {
772
+ const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
773
+ return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
774
+ } catch {
775
+ return false;
776
+ }
777
+ })();
778
+ if (isChatGPT) {
763
779
  agent = "ChatGPT";
764
780
  agentKey = "chatgpt";
765
781
  }
@@ -1056,11 +1072,6 @@ var EdgeAgentDetectorWrapper = class {
1056
1072
  return;
1057
1073
  }
1058
1074
  };
1059
-
1060
- // src/middleware.ts
1061
- init_edge_detector_with_wasm();
1062
-
1063
- // src/session-tracker.ts
1064
1075
  var EdgeSessionTracker = class {
1065
1076
  config;
1066
1077
  constructor(config) {
@@ -1077,7 +1088,7 @@ var EdgeSessionTracker = class {
1077
1088
  */
1078
1089
  async track(_request, response, result) {
1079
1090
  try {
1080
- if (!this.config.enabled || !result.isAgent) {
1091
+ if (!this.config.enabled || !shouldEnforce(result)) {
1081
1092
  return response;
1082
1093
  }
1083
1094
  const sessionData = {
@@ -1152,9 +1163,7 @@ var EdgeSessionTracker = class {
1152
1163
  for (let i = 0; i < encoded.length; i++) {
1153
1164
  obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
1154
1165
  }
1155
- return btoa(
1156
- Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
1157
- );
1166
+ return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
1158
1167
  } catch (error) {
1159
1168
  return btoa(data);
1160
1169
  }
@@ -1179,9 +1188,9 @@ var EdgeSessionTracker = class {
1179
1188
  var StatelessSessionChecker = class {
1180
1189
  static check(headers) {
1181
1190
  try {
1182
- const agent = headers["x-agentshield-session-agent"];
1183
- const confidence = headers["x-agentshield-session-confidence"];
1184
- const sessionId = headers["x-agentshield-session-id"];
1191
+ const agent = headers["kya-session-agent"];
1192
+ const confidence = headers["kya-session-confidence"];
1193
+ const sessionId = headers["kya-session-id"];
1185
1194
  if (agent && confidence && sessionId) {
1186
1195
  return {
1187
1196
  id: sessionId,
@@ -1211,33 +1220,49 @@ var StatelessSessionChecker = class {
1211
1220
  static setHeaders(response, session) {
1212
1221
  try {
1213
1222
  if (response.setHeader) {
1214
- response.setHeader("X-AgentShield-Session-Agent", session.agent);
1215
- response.setHeader(
1216
- "X-AgentShield-Session-Confidence",
1217
- session.confidence.toString()
1218
- );
1219
- response.setHeader("X-AgentShield-Session-Id", session.id);
1223
+ response.setHeader("KYA-Session-Agent", session.agent);
1224
+ response.setHeader("KYA-Session-Confidence", session.confidence.toString());
1225
+ response.setHeader("KYA-Session-Id", session.id);
1220
1226
  } else if (response.headers && response.headers.set) {
1221
- response.headers.set("x-agentshield-session-agent", session.agent);
1222
- response.headers.set(
1223
- "x-agentshield-session-confidence",
1224
- session.confidence.toString()
1225
- );
1226
- response.headers.set("x-agentshield-session-id", session.id);
1227
+ response.headers.set("kya-session-agent", session.agent);
1228
+ response.headers.set("kya-session-confidence", session.confidence.toString());
1229
+ response.headers.set("kya-session-id", session.id);
1227
1230
  }
1228
1231
  } catch {
1229
1232
  }
1230
1233
  }
1231
1234
  };
1232
1235
 
1233
- // src/middleware.ts
1236
+ // src/utils.ts
1237
+ function getClientIp(request) {
1238
+ const forwardedFor = request.headers.get("x-forwarded-for");
1239
+ if (forwardedFor) {
1240
+ const ip = forwardedFor.split(",")[0]?.trim();
1241
+ if (ip) return ip;
1242
+ }
1243
+ const realIp = request.headers.get("x-real-ip");
1244
+ if (realIp) return realIp;
1245
+ const cfIp = request.headers.get("cf-connecting-ip");
1246
+ if (cfIp) return cfIp;
1247
+ const clientIp = request.headers.get("x-client-ip");
1248
+ if (clientIp) return clientIp;
1249
+ return void 0;
1250
+ }
1251
+ function safeHostname(url) {
1252
+ try {
1253
+ return new URL(url).hostname;
1254
+ } catch {
1255
+ return "this site";
1256
+ }
1257
+ }
1234
1258
  function createAgentShieldMiddleware(config = {}) {
1235
- const detector = config.enableWasm ? new EdgeAgentDetectorWrapperWithWasm({ enableWasm: true }) : new EdgeAgentDetectorWrapper(config);
1259
+ let detector = config.enableWasm ? null : new EdgeAgentDetectorWrapper(config);
1260
+ let detectorInitPromise = null;
1236
1261
  const sessionTracker = config.sessionTracking?.enabled || config.enableWasm ? new EdgeSessionTracker({
1237
1262
  enabled: true,
1238
1263
  ...config.sessionTracking
1239
1264
  }) : null;
1240
- if (config.events) {
1265
+ if (detector && config.events) {
1241
1266
  Object.entries(config.events).forEach(([event, handler]) => {
1242
1267
  if (handler) {
1243
1268
  detector.on(event, handler);
@@ -1258,6 +1283,23 @@ function createAgentShieldMiddleware(config = {}) {
1258
1283
  } = config;
1259
1284
  return async (request) => {
1260
1285
  try {
1286
+ if (!detector) {
1287
+ if (!detectorInitPromise) {
1288
+ detectorInitPromise = (async () => {
1289
+ const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
1290
+ detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
1291
+ if (config.events) {
1292
+ Object.entries(config.events).forEach(([event, handler]) => {
1293
+ if (handler) {
1294
+ detector.on(event, handler);
1295
+ }
1296
+ });
1297
+ }
1298
+ })();
1299
+ }
1300
+ await detectorInitPromise;
1301
+ }
1302
+ const activeDetector = detector;
1261
1303
  const shouldSkip = skipPaths.some((pattern) => {
1262
1304
  if (typeof pattern === "string") {
1263
1305
  return request.nextUrl.pathname.startsWith(pattern);
@@ -1271,11 +1313,11 @@ function createAgentShieldMiddleware(config = {}) {
1271
1313
  const existingSession = sessionTracker ? await sessionTracker.check(request) : null;
1272
1314
  if (existingSession) {
1273
1315
  const response2 = NextResponse.next();
1274
- response2.headers.set("x-agentshield-detected", "true");
1275
- response2.headers.set("x-agentshield-agent", existingSession.agent);
1276
- response2.headers.set("x-agentshield-confidence", existingSession.confidence.toString());
1277
- response2.headers.set("x-agentshield-session", "continued");
1278
- response2.headers.set("x-agentshield-session-id", existingSession.id);
1316
+ response2.headers.set("kya-detected", "true");
1317
+ response2.headers.set("kya-agent", existingSession.agent);
1318
+ response2.headers.set("kya-confidence", existingSession.confidence.toString());
1319
+ response2.headers.set("kya-session", "continued");
1320
+ response2.headers.set("kya-session-id", existingSession.id);
1279
1321
  request.agentShield = {
1280
1322
  result: {
1281
1323
  isAgent: true,
@@ -1295,17 +1337,17 @@ function createAgentShieldMiddleware(config = {}) {
1295
1337
  };
1296
1338
  const context2 = {
1297
1339
  userAgent: request.headers.get("user-agent") || "",
1298
- ipAddress: (request.ip ?? request.headers.get("x-forwarded-for")) || "",
1340
+ ipAddress: getClientIp(request) || "",
1299
1341
  headers: Object.fromEntries(request.headers.entries()),
1300
1342
  url: request.url,
1301
1343
  method: request.method,
1302
1344
  timestamp: /* @__PURE__ */ new Date()
1303
1345
  };
1304
- detector.emit("agent.session.continued", existingSession, context2);
1346
+ activeDetector.emit("agent.session.continued", existingSession, context2);
1305
1347
  return response2;
1306
1348
  }
1307
1349
  const userAgent = request.headers.get("user-agent");
1308
- const ipAddress = request.ip ?? request.headers.get("x-forwarded-for");
1350
+ const ipAddress = getClientIp(request);
1309
1351
  const url = new URL(request.url);
1310
1352
  const pathWithQuery = url.pathname + url.search;
1311
1353
  const context = {
@@ -1317,15 +1359,19 @@ function createAgentShieldMiddleware(config = {}) {
1317
1359
  method: request.method,
1318
1360
  timestamp: /* @__PURE__ */ new Date()
1319
1361
  };
1320
- const result = await detector.analyze(context);
1321
- if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
1362
+ const result = await activeDetector.analyze(context);
1363
+ const decision = evaluateEnforcement(result, {
1364
+ confidenceThreshold: config.confidenceThreshold,
1365
+ defaultAction: onAgentDetected
1366
+ });
1367
+ if (decision.shouldNotify) {
1322
1368
  if (onDetection) {
1323
1369
  const customResponse = await onDetection(request, result);
1324
1370
  if (customResponse) {
1325
1371
  return customResponse;
1326
1372
  }
1327
1373
  }
1328
- switch (onAgentDetected) {
1374
+ switch (decision.action) {
1329
1375
  case "block": {
1330
1376
  const response2 = NextResponse.json(
1331
1377
  {
@@ -1341,11 +1387,17 @@ function createAgentShieldMiddleware(config = {}) {
1341
1387
  response2.headers.set(key, value);
1342
1388
  });
1343
1389
  }
1344
- detector.emit("agent.blocked", result, context);
1390
+ activeDetector.emit("agent.blocked", result, context);
1345
1391
  return response2;
1346
1392
  }
1347
- case "redirect":
1348
- return NextResponse.redirect(new URL(redirectUrl, request.url));
1393
+ case "redirect": {
1394
+ const redirectTarget = new URL(redirectUrl, request.url);
1395
+ const agentName = result.detectedAgent?.name;
1396
+ if (agentName && !redirectTarget.searchParams.has("agent")) {
1397
+ redirectTarget.searchParams.set("agent", agentName.toLowerCase());
1398
+ }
1399
+ return NextResponse.redirect(redirectTarget);
1400
+ }
1349
1401
  case "rewrite":
1350
1402
  return NextResponse.rewrite(new URL(rewriteUrl, request.url));
1351
1403
  case "log":
@@ -1361,9 +1413,7 @@ function createAgentShieldMiddleware(config = {}) {
1361
1413
  break;
1362
1414
  case "allow":
1363
1415
  default:
1364
- if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
1365
- detector.emit("agent.allowed", result, context);
1366
- }
1416
+ activeDetector.emit("agent.allowed", result, context);
1367
1417
  break;
1368
1418
  }
1369
1419
  }
@@ -1372,15 +1422,15 @@ function createAgentShieldMiddleware(config = {}) {
1372
1422
  skipped: false
1373
1423
  };
1374
1424
  let response = NextResponse.next();
1375
- response.headers.set("x-agentshield-detected", result.isAgent.toString());
1376
- response.headers.set("x-agentshield-confidence", result.confidence.toString());
1425
+ response.headers.set("kya-detected", result.isAgent.toString());
1426
+ response.headers.set("kya-confidence", result.confidence.toString());
1377
1427
  if (result.detectedAgent?.name) {
1378
- response.headers.set("x-agentshield-agent", result.detectedAgent.name);
1428
+ response.headers.set("kya-agent", result.detectedAgent.name);
1379
1429
  }
1380
- if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
1430
+ if (sessionTracker && decision.shouldNotify) {
1381
1431
  response = await sessionTracker.track(request, response, result);
1382
- response.headers.set("x-agentshield-session", "new");
1383
- detector.emit("agent.session.started", result, context);
1432
+ response.headers.set("kya-session", "new");
1433
+ activeDetector.emit("agent.session.started", result, context);
1384
1434
  }
1385
1435
  return response;
1386
1436
  } catch (error) {
@@ -1721,8 +1771,6 @@ async function createStorageAdapter(config) {
1721
1771
  }
1722
1772
  return new MemoryStorageAdapter();
1723
1773
  }
1724
-
1725
- // src/enhanced-middleware.ts
1726
1774
  var SessionManager = class {
1727
1775
  sessionLastActivity = /* @__PURE__ */ new Map();
1728
1776
  generateSessionId(ipAddress, userAgent) {
@@ -1829,7 +1877,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
1829
1877
  return NextResponse.next();
1830
1878
  }
1831
1879
  const userAgent = request.headers.get("user-agent");
1832
- const ipAddress = request.ip ?? request.headers.get("x-forwarded-for");
1880
+ const ipAddress = getClientIp(request);
1833
1881
  const url = new URL(request.url);
1834
1882
  const pathWithQuery = url.pathname + url.search;
1835
1883
  const context = {
@@ -1844,14 +1892,21 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
1844
1892
  const result = await activeDetector.analyze(context);
1845
1893
  let finalConfidence = result.confidence;
1846
1894
  let verificationMethod = result.verificationMethod || "pattern";
1847
- if (result.isAgent && wasmConfidenceUtils) {
1895
+ if (shouldEnforce(result) && wasmConfidenceUtils) {
1848
1896
  const reasons = result.reasons || [];
1849
1897
  if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) {
1850
1898
  finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons);
1851
1899
  verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons);
1852
1900
  }
1853
1901
  }
1854
- if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
1902
+ const decision = evaluateEnforcement(
1903
+ { ...result, confidence: finalConfidence },
1904
+ {
1905
+ confidenceThreshold: config.confidenceThreshold,
1906
+ defaultAction: config.onAgentDetected
1907
+ }
1908
+ );
1909
+ if (decision.shouldNotify) {
1855
1910
  if (sessionTrackingEnabled) {
1856
1911
  const storage = await getStorage();
1857
1912
  const sessionId = sessionManager.generateSessionId(
@@ -1915,26 +1970,23 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
1915
1970
  if (config.onDetection) {
1916
1971
  await config.onDetection(result, context);
1917
1972
  }
1918
- switch (config.onAgentDetected) {
1973
+ switch (decision.action) {
1919
1974
  case "block": {
1920
1975
  const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {};
1921
1976
  const response2 = NextResponse.json(
1922
1977
  { error: message, detected: true, confidence: finalConfidence },
1923
1978
  { status }
1924
1979
  );
1925
- response2.headers.set("x-agentshield-detected", "true");
1926
- response2.headers.set(
1927
- "x-agentshield-confidence",
1928
- String(Math.round(finalConfidence * 100))
1929
- );
1930
- response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
1931
- response2.headers.set("x-agentshield-verification", verificationMethod);
1980
+ response2.headers.set("kya-detected", "true");
1981
+ response2.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
1982
+ response2.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
1983
+ response2.headers.set("kya-verification", verificationMethod);
1932
1984
  return response2;
1933
1985
  }
1934
1986
  case "log": {
1935
1987
  const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
1936
1988
  if (isInteresting && process.env.NODE_ENV !== "production") {
1937
- console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
1989
+ console.debug(`[AgentShield] AI Agent detected (${verificationMethod}):`, {
1938
1990
  agent: result.detectedAgent?.name,
1939
1991
  confidence: `${(finalConfidence * 100).toFixed(0)}%`,
1940
1992
  path: pathWithQuery,
@@ -1947,14 +1999,14 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
1947
1999
  }
1948
2000
  const response = NextResponse.next();
1949
2001
  if (result.isAgent) {
1950
- response.headers.set("x-agentshield-detected", "true");
1951
- response.headers.set("x-agentshield-confidence", String(Math.round(finalConfidence * 100)));
1952
- response.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
1953
- response.headers.set("x-agentshield-verification", verificationMethod);
2002
+ response.headers.set("kya-detected", "true");
2003
+ response.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
2004
+ response.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
2005
+ response.headers.set("kya-verification", verificationMethod);
1954
2006
  if (finalConfidence > 0.9) {
1955
- response.headers.set("x-ai-visitor", "true");
1956
- response.headers.set("x-ai-confidence", finalConfidence.toString());
1957
- response.headers.set("x-ai-verification", verificationMethod);
2007
+ response.headers.set("kya-ai-visitor", "true");
2008
+ response.headers.set("kya-ai-confidence", finalConfidence.toString());
2009
+ response.headers.set("kya-ai-verification", verificationMethod);
1958
2010
  }
1959
2011
  }
1960
2012
  return response;
@@ -2153,6 +2205,80 @@ function getAgentShieldClient(config) {
2153
2205
  function resetAgentShieldClient() {
2154
2206
  clientInstance = null;
2155
2207
  }
2208
+ var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
2209
+ var DEFAULT_CONNECT_PATH = "/connect";
2210
+ function buildAgentInstructionResponse(request, decision, redirectUrl) {
2211
+ const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
2212
+ const agentName = decision.agentName || decision.agentType || "unknown";
2213
+ if (!resolved.searchParams.has("agent")) {
2214
+ resolved.searchParams.set("agent", agentName.toLowerCase());
2215
+ }
2216
+ const authUrl = resolved.toString();
2217
+ const hostname = safeHostname(request.url);
2218
+ const body = {
2219
+ // Markdown-formatted so clients that render markdown (Claude Desktop,
2220
+ // ChatGPT web) surface the URL as a clickable link. Tone mirrors the
2221
+ // gateway response so messaging stays consistent across platforms.
2222
+ message: `I can't access ${hostname} yet \u2014 this site checks AI assistants at the front door.
2223
+
2224
+ **To give me access, open this link:**
2225
+ [Connect securely to ${hostname}](${authUrl})
2226
+
2227
+ 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.`,
2228
+ user_action_required: {
2229
+ action: `Connect securely to ${hostname}`,
2230
+ url: authUrl,
2231
+ reason: `${hostname} checks AI assistants before they connect. Open the link to give your assistant a verified key.`
2232
+ },
2233
+ mcp_i: {
2234
+ version: "1.0",
2235
+ action: "authenticate",
2236
+ authorization_url: authUrl,
2237
+ flow: {
2238
+ type: "oauth2_delegation",
2239
+ steps: [
2240
+ "1. Direct your user to the authorization_url",
2241
+ "2. User reviews requested scopes and grants consent",
2242
+ "3. Receive delegation credential (JWT)",
2243
+ "4. Include credential in KYA-Delegation header",
2244
+ "5. Retry this request with the proof"
2245
+ ]
2246
+ },
2247
+ retry_instructions: {
2248
+ header: "KYA-Delegation",
2249
+ format: "JWT delegation credential from authorization flow"
2250
+ },
2251
+ documentation: MCP_I_DOCS_URL
2252
+ },
2253
+ error: "mcp_authentication_required",
2254
+ code: "AGENT_REQUIRES_DELEGATION",
2255
+ detection: {
2256
+ agent_type: decision.agentType || "ai_agent",
2257
+ agent_name: decision.agentName || "Unknown Agent",
2258
+ confidence: decision.confidence
2259
+ }
2260
+ };
2261
+ const response = NextResponse.json(body, { status: 401 });
2262
+ response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
2263
+ response.headers.set(
2264
+ "Link",
2265
+ `<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
2266
+ );
2267
+ response.headers.set("KYA-Auth-Required", "true");
2268
+ response.headers.set("KYA-Auth-Url", authUrl);
2269
+ response.headers.set("KYA-Action", "instruct");
2270
+ response.headers.set("KYA-Detected-Agent", agentName);
2271
+ response.headers.set("KYA-Confidence", decision.confidence.toString());
2272
+ response.headers.set("Cache-Control", "no-store");
2273
+ return response;
2274
+ }
2275
+ function resolveUrl(target, baseUrl2) {
2276
+ try {
2277
+ return new URL(target, baseUrl2);
2278
+ } catch {
2279
+ return new URL(DEFAULT_CONNECT_PATH, baseUrl2);
2280
+ }
2281
+ }
2156
2282
 
2157
2283
  // src/api-middleware.ts
2158
2284
  function matchPath(path, pattern) {
@@ -2173,27 +2299,52 @@ function shouldIncludePath(path, includePaths) {
2173
2299
  if (!includePaths || includePaths.length === 0) return true;
2174
2300
  return includePaths.some((pattern) => matchPath(path, pattern));
2175
2301
  }
2176
- function buildBlockedResponse(decision, config) {
2302
+ function buildBlockedResponse(request, decision, config) {
2177
2303
  const status = config.blockedResponse?.status ?? 403;
2178
2304
  const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
2179
- const response = NextResponse.json(
2180
- {
2181
- error: message,
2182
- code: "AGENT_BLOCKED",
2183
- reason: decision.reason,
2184
- agentType: decision.agentType
2185
- },
2186
- { status }
2187
- );
2305
+ const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
2306
+ const body = {
2307
+ error: message,
2308
+ code: "AGENT_BLOCKED",
2309
+ reason: decision.reason,
2310
+ agentType: decision.agentType
2311
+ };
2312
+ if (recoveryUrl) {
2313
+ const hostname = safeHostname(request.url);
2314
+ body.user_action_required = {
2315
+ action: `Connect securely to ${hostname}`,
2316
+ url: recoveryUrl,
2317
+ reason: `${hostname} blocks unverified AI assistants. Open the link to give your assistant a verified key and try again.`
2318
+ };
2319
+ body.message = `I can't access ${hostname} \u2014 this site blocks unverified AI assistants.
2320
+
2321
+ **To give me access, open this link:**
2322
+ [Connect securely to ${hostname}](${recoveryUrl})
2323
+
2324
+ Once you're done, ask me to try again.`;
2325
+ }
2326
+ const response = NextResponse.json(body, { status });
2188
2327
  if (config.blockedResponse?.headers) {
2189
2328
  for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
2190
2329
  response.headers.set(key, value);
2191
2330
  }
2192
2331
  }
2193
- response.headers.set("X-AgentShield-Action", decision.action);
2194
- response.headers.set("X-AgentShield-Reason", decision.reason);
2332
+ if (recoveryUrl) {
2333
+ response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
2334
+ response.headers.set("KYA-Auth-Url", recoveryUrl);
2335
+ }
2336
+ response.headers.set("KYA-Action", decision.action);
2337
+ response.headers.set("KYA-Reason", decision.reason);
2195
2338
  return response;
2196
2339
  }
2340
+ function resolveRecoveryUrl(request, target) {
2341
+ if (!target) return void 0;
2342
+ try {
2343
+ return new URL(target, request.url).toString();
2344
+ } catch {
2345
+ return void 0;
2346
+ }
2347
+ }
2197
2348
  function buildRedirectResponse(request, decision, config) {
2198
2349
  const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
2199
2350
  const url = new URL(redirectUrl, request.url);
@@ -2238,7 +2389,7 @@ function withAgentShield(config = {}) {
2238
2389
  try {
2239
2390
  const client2 = getClient();
2240
2391
  const userAgent = request.headers.get("user-agent") || void 0;
2241
- const ipAddress = request.ip || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || void 0;
2392
+ const ipAddress = getClientIp(request);
2242
2393
  const result = await client2.enforce({
2243
2394
  headers: Object.fromEntries(request.headers.entries()),
2244
2395
  userAgent,
@@ -2289,6 +2440,7 @@ function withAgentShield(config = {}) {
2289
2440
  if (decision.isAgent && config.onAgentDetected) {
2290
2441
  await config.onAgentDetected(request, decision);
2291
2442
  }
2443
+ const redirectMode = config.redirectMode ?? "instruct";
2292
2444
  switch (decision.action) {
2293
2445
  case "block": {
2294
2446
  if (config.customBlockedResponse) {
@@ -2297,10 +2449,15 @@ function withAgentShield(config = {}) {
2297
2449
  if (config.onBlock === "redirect") {
2298
2450
  return buildRedirectResponse(request, decision, config);
2299
2451
  }
2300
- return buildBlockedResponse(decision, config);
2452
+ return buildBlockedResponse(request, decision, config);
2301
2453
  }
2302
- case "redirect": {
2303
- return buildRedirectResponse(request, decision, config);
2454
+ case "redirect":
2455
+ case "instruct": {
2456
+ if (redirectMode === "http" && decision.action === "redirect") {
2457
+ return buildRedirectResponse(request, decision, config);
2458
+ }
2459
+ const targetUrl = config.redirectUrl || decision.redirectUrl;
2460
+ return buildAgentInstructionResponse(request, decision, targetUrl);
2304
2461
  }
2305
2462
  case "challenge": {
2306
2463
  return buildRedirectResponse(request, decision, config);
@@ -2310,10 +2467,10 @@ function withAgentShield(config = {}) {
2310
2467
  default: {
2311
2468
  const response = NextResponse.next();
2312
2469
  if (decision.isAgent) {
2313
- response.headers.set("X-AgentShield-Detected", "true");
2314
- response.headers.set("X-AgentShield-Confidence", decision.confidence.toString());
2470
+ response.headers.set("KYA-Detected", "true");
2471
+ response.headers.set("KYA-Confidence", decision.confidence.toString());
2315
2472
  if (decision.agentName) {
2316
- response.headers.set("X-AgentShield-Agent", decision.agentName);
2473
+ response.headers.set("KYA-Agent", decision.agentName);
2317
2474
  }
2318
2475
  }
2319
2476
  return response;
@@ -2371,24 +2528,28 @@ function buildBlockedResponse2(decision, config) {
2371
2528
  response.headers.set(key, value);
2372
2529
  }
2373
2530
  }
2374
- response.headers.set("X-AgentShield-Action", decision.action);
2375
- response.headers.set("X-AgentShield-Reason", decision.reason);
2376
- response.headers.set("X-AgentShield-MatchType", decision.matchType);
2531
+ response.headers.set("KYA-Action", decision.action);
2532
+ response.headers.set("KYA-Reason", decision.reason);
2533
+ response.headers.set("KYA-Match-Type", decision.matchType);
2377
2534
  return response;
2378
2535
  }
2379
- function buildRedirectResponse2(request, decision, config) {
2536
+ function buildRedirectResponse2(request, decision, config, detection) {
2380
2537
  const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
2381
2538
  const url = new URL(redirectUrl, request.url);
2382
2539
  url.searchParams.set("reason", decision.reason);
2383
2540
  if (decision.ruleId) {
2384
2541
  url.searchParams.set("ruleId", decision.ruleId);
2385
2542
  }
2543
+ const agentName = detection?.detectedAgent?.name;
2544
+ if (agentName && !url.searchParams.has("agent")) {
2545
+ url.searchParams.set("agent", agentName.toLowerCase());
2546
+ }
2386
2547
  return NextResponse.redirect(url);
2387
2548
  }
2388
- function buildChallengeResponse(request, decision, config) {
2389
- return buildRedirectResponse2(request, decision, config);
2549
+ function buildChallengeResponse(request, decision, config, detection) {
2550
+ return buildRedirectResponse2(request, decision, config, detection);
2390
2551
  }
2391
- async function handlePolicyDecision(request, decision, config) {
2552
+ async function handlePolicyDecision(request, decision, config, detection) {
2392
2553
  switch (decision.action) {
2393
2554
  case ENFORCEMENT_ACTIONS.BLOCK:
2394
2555
  if (config.customBlockedResponse) {
@@ -2396,9 +2557,9 @@ async function handlePolicyDecision(request, decision, config) {
2396
2557
  }
2397
2558
  return buildBlockedResponse2(decision, config);
2398
2559
  case ENFORCEMENT_ACTIONS.REDIRECT:
2399
- return buildRedirectResponse2(request, decision, config);
2560
+ return buildRedirectResponse2(request, decision, config, detection);
2400
2561
  case ENFORCEMENT_ACTIONS.CHALLENGE:
2401
- return buildChallengeResponse(request, decision, config);
2562
+ return buildChallengeResponse(request, decision, config, detection);
2402
2563
  case ENFORCEMENT_ACTIONS.LOG:
2403
2564
  console.log("[AgentShield] Policy decision (log):", {
2404
2565
  path: request.nextUrl.pathname,
@@ -2472,7 +2633,7 @@ async function applyPolicy(request, detection, config) {
2472
2633
  if (config.onPolicyDecision) {
2473
2634
  await config.onPolicyDecision(request, decision, context);
2474
2635
  }
2475
- return await handlePolicyDecision(request, decision, config);
2636
+ return await handlePolicyDecision(request, decision, config, detection);
2476
2637
  } catch (error) {
2477
2638
  if (config.debug) {
2478
2639
  console.error("[AgentShield] Policy evaluation error:", error);