@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.
- package/dist/api-client.d.mts +6 -1
- package/dist/api-client.d.ts +6 -1
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.mjs.map +1 -1
- package/dist/api-middleware.d.mts +13 -0
- package/dist/api-middleware.d.ts +13 -0
- package/dist/api-middleware.js +149 -19
- package/dist/api-middleware.js.map +1 -1
- package/dist/api-middleware.mjs +149 -19
- package/dist/api-middleware.mjs.map +1 -1
- package/dist/create-middleware.js +565 -487
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +565 -487
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge/index.js +69 -46
- package/dist/edge/index.js.map +1 -1
- package/dist/edge/index.mjs +69 -46
- package/dist/edge/index.mjs.map +1 -1
- package/dist/edge-detector-wrapper.js +9 -1
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +9 -1
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/edge-runtime-loader.d.mts +1 -0
- package/dist/edge-runtime-loader.d.ts +1 -0
- package/dist/edge-runtime-loader.js +19 -3
- package/dist/edge-runtime-loader.js.map +1 -1
- package/dist/edge-runtime-loader.mjs +19 -3
- package/dist/edge-runtime-loader.mjs.map +1 -1
- package/dist/edge-wasm-middleware.d.mts +1 -0
- package/dist/edge-wasm-middleware.d.ts +1 -0
- package/dist/edge-wasm-middleware.js +10 -2
- package/dist/edge-wasm-middleware.js.map +1 -1
- package/dist/edge-wasm-middleware.mjs +11 -3
- package/dist/edge-wasm-middleware.mjs.map +1 -1
- package/dist/enhanced-middleware.js +48 -20
- package/dist/enhanced-middleware.js.map +1 -1
- package/dist/enhanced-middleware.mjs +49 -21
- package/dist/enhanced-middleware.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +263 -102
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +264 -103
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +565 -487
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +565 -487
- package/dist/middleware.mjs.map +1 -1
- package/dist/policy.d.mts +16 -4
- package/dist/policy.d.ts +16 -4
- package/dist/policy.js +14 -10
- package/dist/policy.js.map +1 -1
- package/dist/policy.mjs +14 -10
- package/dist/policy.mjs.map +1 -1
- package/dist/session-tracker.js +13 -19
- package/dist/session-tracker.js.map +1 -1
- package/dist/session-tracker.mjs +13 -19
- package/dist/session-tracker.mjs.map +1 -1
- package/dist/signature-verifier.js +9 -1
- package/dist/signature-verifier.js.map +1 -1
- package/dist/signature-verifier.mjs +9 -1
- package/dist/signature-verifier.mjs.map +1 -1
- package/dist/wasm-middleware.d.mts +1 -0
- package/dist/wasm-middleware.d.ts +1 -0
- package/dist/wasm-middleware.js +15 -15
- package/dist/wasm-middleware.js.map +1 -1
- package/dist/wasm-middleware.mjs +15 -15
- package/dist/wasm-middleware.mjs.map +1 -1
- package/package.json +23 -22
- package/wasm/agentshield_wasm.d.ts +379 -21
- package/wasm/agentshield_wasm.js +1409 -506
- package/wasm/agentshield_wasm_bg.wasm +0 -0
package/dist/index.js
CHANGED
|
@@ -253,7 +253,15 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
253
253
|
}
|
|
254
254
|
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
255
255
|
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
256
|
-
|
|
256
|
+
const isChatGPT = (() => {
|
|
257
|
+
try {
|
|
258
|
+
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
259
|
+
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
})();
|
|
264
|
+
if (isChatGPT) {
|
|
257
265
|
confidence = 85;
|
|
258
266
|
reasons.push("signature_agent:chatgpt");
|
|
259
267
|
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
@@ -780,7 +788,15 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
780
788
|
}
|
|
781
789
|
let agent;
|
|
782
790
|
let agentKey;
|
|
783
|
-
|
|
791
|
+
const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
|
|
792
|
+
try {
|
|
793
|
+
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
794
|
+
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
795
|
+
} catch {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
})();
|
|
799
|
+
if (isChatGPT) {
|
|
784
800
|
agent = "ChatGPT";
|
|
785
801
|
agentKey = "chatgpt";
|
|
786
802
|
}
|
|
@@ -1077,11 +1093,6 @@ var EdgeAgentDetectorWrapper = class {
|
|
|
1077
1093
|
return;
|
|
1078
1094
|
}
|
|
1079
1095
|
};
|
|
1080
|
-
|
|
1081
|
-
// src/middleware.ts
|
|
1082
|
-
init_edge_detector_with_wasm();
|
|
1083
|
-
|
|
1084
|
-
// src/session-tracker.ts
|
|
1085
1096
|
var EdgeSessionTracker = class {
|
|
1086
1097
|
config;
|
|
1087
1098
|
constructor(config) {
|
|
@@ -1098,7 +1109,7 @@ var EdgeSessionTracker = class {
|
|
|
1098
1109
|
*/
|
|
1099
1110
|
async track(_request, response, result) {
|
|
1100
1111
|
try {
|
|
1101
|
-
if (!this.config.enabled || !result
|
|
1112
|
+
if (!this.config.enabled || !agentshieldShared.shouldEnforce(result)) {
|
|
1102
1113
|
return response;
|
|
1103
1114
|
}
|
|
1104
1115
|
const sessionData = {
|
|
@@ -1173,9 +1184,7 @@ var EdgeSessionTracker = class {
|
|
|
1173
1184
|
for (let i = 0; i < encoded.length; i++) {
|
|
1174
1185
|
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
1175
1186
|
}
|
|
1176
|
-
return btoa(
|
|
1177
|
-
Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
|
|
1178
|
-
);
|
|
1187
|
+
return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
|
|
1179
1188
|
} catch (error) {
|
|
1180
1189
|
return btoa(data);
|
|
1181
1190
|
}
|
|
@@ -1200,9 +1209,9 @@ var EdgeSessionTracker = class {
|
|
|
1200
1209
|
var StatelessSessionChecker = class {
|
|
1201
1210
|
static check(headers) {
|
|
1202
1211
|
try {
|
|
1203
|
-
const agent = headers["
|
|
1204
|
-
const confidence = headers["
|
|
1205
|
-
const sessionId = headers["
|
|
1212
|
+
const agent = headers["kya-session-agent"];
|
|
1213
|
+
const confidence = headers["kya-session-confidence"];
|
|
1214
|
+
const sessionId = headers["kya-session-id"];
|
|
1206
1215
|
if (agent && confidence && sessionId) {
|
|
1207
1216
|
return {
|
|
1208
1217
|
id: sessionId,
|
|
@@ -1232,33 +1241,49 @@ var StatelessSessionChecker = class {
|
|
|
1232
1241
|
static setHeaders(response, session) {
|
|
1233
1242
|
try {
|
|
1234
1243
|
if (response.setHeader) {
|
|
1235
|
-
response.setHeader("
|
|
1236
|
-
response.setHeader(
|
|
1237
|
-
|
|
1238
|
-
session.confidence.toString()
|
|
1239
|
-
);
|
|
1240
|
-
response.setHeader("X-AgentShield-Session-Id", session.id);
|
|
1244
|
+
response.setHeader("KYA-Session-Agent", session.agent);
|
|
1245
|
+
response.setHeader("KYA-Session-Confidence", session.confidence.toString());
|
|
1246
|
+
response.setHeader("KYA-Session-Id", session.id);
|
|
1241
1247
|
} else if (response.headers && response.headers.set) {
|
|
1242
|
-
response.headers.set("
|
|
1243
|
-
response.headers.set(
|
|
1244
|
-
|
|
1245
|
-
session.confidence.toString()
|
|
1246
|
-
);
|
|
1247
|
-
response.headers.set("x-agentshield-session-id", session.id);
|
|
1248
|
+
response.headers.set("kya-session-agent", session.agent);
|
|
1249
|
+
response.headers.set("kya-session-confidence", session.confidence.toString());
|
|
1250
|
+
response.headers.set("kya-session-id", session.id);
|
|
1248
1251
|
}
|
|
1249
1252
|
} catch {
|
|
1250
1253
|
}
|
|
1251
1254
|
}
|
|
1252
1255
|
};
|
|
1253
1256
|
|
|
1254
|
-
// src/
|
|
1257
|
+
// src/utils.ts
|
|
1258
|
+
function getClientIp(request) {
|
|
1259
|
+
const forwardedFor = request.headers.get("x-forwarded-for");
|
|
1260
|
+
if (forwardedFor) {
|
|
1261
|
+
const ip = forwardedFor.split(",")[0]?.trim();
|
|
1262
|
+
if (ip) return ip;
|
|
1263
|
+
}
|
|
1264
|
+
const realIp = request.headers.get("x-real-ip");
|
|
1265
|
+
if (realIp) return realIp;
|
|
1266
|
+
const cfIp = request.headers.get("cf-connecting-ip");
|
|
1267
|
+
if (cfIp) return cfIp;
|
|
1268
|
+
const clientIp = request.headers.get("x-client-ip");
|
|
1269
|
+
if (clientIp) return clientIp;
|
|
1270
|
+
return void 0;
|
|
1271
|
+
}
|
|
1272
|
+
function safeHostname(url) {
|
|
1273
|
+
try {
|
|
1274
|
+
return new URL(url).hostname;
|
|
1275
|
+
} catch {
|
|
1276
|
+
return "this site";
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1255
1279
|
function createAgentShieldMiddleware(config = {}) {
|
|
1256
|
-
|
|
1280
|
+
let detector = config.enableWasm ? null : new EdgeAgentDetectorWrapper(config);
|
|
1281
|
+
let detectorInitPromise = null;
|
|
1257
1282
|
const sessionTracker = config.sessionTracking?.enabled || config.enableWasm ? new EdgeSessionTracker({
|
|
1258
1283
|
enabled: true,
|
|
1259
1284
|
...config.sessionTracking
|
|
1260
1285
|
}) : null;
|
|
1261
|
-
if (config.events) {
|
|
1286
|
+
if (detector && config.events) {
|
|
1262
1287
|
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1263
1288
|
if (handler) {
|
|
1264
1289
|
detector.on(event, handler);
|
|
@@ -1279,6 +1304,23 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1279
1304
|
} = config;
|
|
1280
1305
|
return async (request) => {
|
|
1281
1306
|
try {
|
|
1307
|
+
if (!detector) {
|
|
1308
|
+
if (!detectorInitPromise) {
|
|
1309
|
+
detectorInitPromise = (async () => {
|
|
1310
|
+
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1311
|
+
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1312
|
+
if (config.events) {
|
|
1313
|
+
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1314
|
+
if (handler) {
|
|
1315
|
+
detector.on(event, handler);
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
})();
|
|
1320
|
+
}
|
|
1321
|
+
await detectorInitPromise;
|
|
1322
|
+
}
|
|
1323
|
+
const activeDetector = detector;
|
|
1282
1324
|
const shouldSkip = skipPaths.some((pattern) => {
|
|
1283
1325
|
if (typeof pattern === "string") {
|
|
1284
1326
|
return request.nextUrl.pathname.startsWith(pattern);
|
|
@@ -1292,11 +1334,11 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1292
1334
|
const existingSession = sessionTracker ? await sessionTracker.check(request) : null;
|
|
1293
1335
|
if (existingSession) {
|
|
1294
1336
|
const response2 = server.NextResponse.next();
|
|
1295
|
-
response2.headers.set("
|
|
1296
|
-
response2.headers.set("
|
|
1297
|
-
response2.headers.set("
|
|
1298
|
-
response2.headers.set("
|
|
1299
|
-
response2.headers.set("
|
|
1337
|
+
response2.headers.set("kya-detected", "true");
|
|
1338
|
+
response2.headers.set("kya-agent", existingSession.agent);
|
|
1339
|
+
response2.headers.set("kya-confidence", existingSession.confidence.toString());
|
|
1340
|
+
response2.headers.set("kya-session", "continued");
|
|
1341
|
+
response2.headers.set("kya-session-id", existingSession.id);
|
|
1300
1342
|
request.agentShield = {
|
|
1301
1343
|
result: {
|
|
1302
1344
|
isAgent: true,
|
|
@@ -1316,17 +1358,17 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1316
1358
|
};
|
|
1317
1359
|
const context2 = {
|
|
1318
1360
|
userAgent: request.headers.get("user-agent") || "",
|
|
1319
|
-
ipAddress: (request
|
|
1361
|
+
ipAddress: getClientIp(request) || "",
|
|
1320
1362
|
headers: Object.fromEntries(request.headers.entries()),
|
|
1321
1363
|
url: request.url,
|
|
1322
1364
|
method: request.method,
|
|
1323
1365
|
timestamp: /* @__PURE__ */ new Date()
|
|
1324
1366
|
};
|
|
1325
|
-
|
|
1367
|
+
activeDetector.emit("agent.session.continued", existingSession, context2);
|
|
1326
1368
|
return response2;
|
|
1327
1369
|
}
|
|
1328
1370
|
const userAgent = request.headers.get("user-agent");
|
|
1329
|
-
const ipAddress = request
|
|
1371
|
+
const ipAddress = getClientIp(request);
|
|
1330
1372
|
const url = new URL(request.url);
|
|
1331
1373
|
const pathWithQuery = url.pathname + url.search;
|
|
1332
1374
|
const context = {
|
|
@@ -1338,15 +1380,19 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1338
1380
|
method: request.method,
|
|
1339
1381
|
timestamp: /* @__PURE__ */ new Date()
|
|
1340
1382
|
};
|
|
1341
|
-
const result = await
|
|
1342
|
-
|
|
1383
|
+
const result = await activeDetector.analyze(context);
|
|
1384
|
+
const decision = agentshieldShared.evaluateEnforcement(result, {
|
|
1385
|
+
confidenceThreshold: config.confidenceThreshold,
|
|
1386
|
+
defaultAction: onAgentDetected
|
|
1387
|
+
});
|
|
1388
|
+
if (decision.shouldNotify) {
|
|
1343
1389
|
if (onDetection) {
|
|
1344
1390
|
const customResponse = await onDetection(request, result);
|
|
1345
1391
|
if (customResponse) {
|
|
1346
1392
|
return customResponse;
|
|
1347
1393
|
}
|
|
1348
1394
|
}
|
|
1349
|
-
switch (
|
|
1395
|
+
switch (decision.action) {
|
|
1350
1396
|
case "block": {
|
|
1351
1397
|
const response2 = server.NextResponse.json(
|
|
1352
1398
|
{
|
|
@@ -1362,11 +1408,17 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1362
1408
|
response2.headers.set(key, value);
|
|
1363
1409
|
});
|
|
1364
1410
|
}
|
|
1365
|
-
|
|
1411
|
+
activeDetector.emit("agent.blocked", result, context);
|
|
1366
1412
|
return response2;
|
|
1367
1413
|
}
|
|
1368
|
-
case "redirect":
|
|
1369
|
-
|
|
1414
|
+
case "redirect": {
|
|
1415
|
+
const redirectTarget = new URL(redirectUrl, request.url);
|
|
1416
|
+
const agentName = result.detectedAgent?.name;
|
|
1417
|
+
if (agentName && !redirectTarget.searchParams.has("agent")) {
|
|
1418
|
+
redirectTarget.searchParams.set("agent", agentName.toLowerCase());
|
|
1419
|
+
}
|
|
1420
|
+
return server.NextResponse.redirect(redirectTarget);
|
|
1421
|
+
}
|
|
1370
1422
|
case "rewrite":
|
|
1371
1423
|
return server.NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1372
1424
|
case "log":
|
|
@@ -1382,9 +1434,7 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1382
1434
|
break;
|
|
1383
1435
|
case "allow":
|
|
1384
1436
|
default:
|
|
1385
|
-
|
|
1386
|
-
detector.emit("agent.allowed", result, context);
|
|
1387
|
-
}
|
|
1437
|
+
activeDetector.emit("agent.allowed", result, context);
|
|
1388
1438
|
break;
|
|
1389
1439
|
}
|
|
1390
1440
|
}
|
|
@@ -1393,15 +1443,15 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1393
1443
|
skipped: false
|
|
1394
1444
|
};
|
|
1395
1445
|
let response = server.NextResponse.next();
|
|
1396
|
-
response.headers.set("
|
|
1397
|
-
response.headers.set("
|
|
1446
|
+
response.headers.set("kya-detected", result.isAgent.toString());
|
|
1447
|
+
response.headers.set("kya-confidence", result.confidence.toString());
|
|
1398
1448
|
if (result.detectedAgent?.name) {
|
|
1399
|
-
response.headers.set("
|
|
1449
|
+
response.headers.set("kya-agent", result.detectedAgent.name);
|
|
1400
1450
|
}
|
|
1401
|
-
if (sessionTracker &&
|
|
1451
|
+
if (sessionTracker && decision.shouldNotify) {
|
|
1402
1452
|
response = await sessionTracker.track(request, response, result);
|
|
1403
|
-
response.headers.set("
|
|
1404
|
-
|
|
1453
|
+
response.headers.set("kya-session", "new");
|
|
1454
|
+
activeDetector.emit("agent.session.started", result, context);
|
|
1405
1455
|
}
|
|
1406
1456
|
return response;
|
|
1407
1457
|
} catch (error) {
|
|
@@ -1742,8 +1792,6 @@ async function createStorageAdapter(config) {
|
|
|
1742
1792
|
}
|
|
1743
1793
|
return new MemoryStorageAdapter();
|
|
1744
1794
|
}
|
|
1745
|
-
|
|
1746
|
-
// src/enhanced-middleware.ts
|
|
1747
1795
|
var SessionManager = class {
|
|
1748
1796
|
sessionLastActivity = /* @__PURE__ */ new Map();
|
|
1749
1797
|
generateSessionId(ipAddress, userAgent) {
|
|
@@ -1850,7 +1898,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1850
1898
|
return server.NextResponse.next();
|
|
1851
1899
|
}
|
|
1852
1900
|
const userAgent = request.headers.get("user-agent");
|
|
1853
|
-
const ipAddress = request
|
|
1901
|
+
const ipAddress = getClientIp(request);
|
|
1854
1902
|
const url = new URL(request.url);
|
|
1855
1903
|
const pathWithQuery = url.pathname + url.search;
|
|
1856
1904
|
const context = {
|
|
@@ -1865,14 +1913,21 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1865
1913
|
const result = await activeDetector.analyze(context);
|
|
1866
1914
|
let finalConfidence = result.confidence;
|
|
1867
1915
|
let verificationMethod = result.verificationMethod || "pattern";
|
|
1868
|
-
if (result
|
|
1916
|
+
if (agentshieldShared.shouldEnforce(result) && wasmConfidenceUtils) {
|
|
1869
1917
|
const reasons = result.reasons || [];
|
|
1870
1918
|
if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) {
|
|
1871
1919
|
finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons);
|
|
1872
1920
|
verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons);
|
|
1873
1921
|
}
|
|
1874
1922
|
}
|
|
1875
|
-
|
|
1923
|
+
const decision = agentshieldShared.evaluateEnforcement(
|
|
1924
|
+
{ ...result, confidence: finalConfidence },
|
|
1925
|
+
{
|
|
1926
|
+
confidenceThreshold: config.confidenceThreshold,
|
|
1927
|
+
defaultAction: config.onAgentDetected
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
if (decision.shouldNotify) {
|
|
1876
1931
|
if (sessionTrackingEnabled) {
|
|
1877
1932
|
const storage = await getStorage();
|
|
1878
1933
|
const sessionId = sessionManager.generateSessionId(
|
|
@@ -1936,26 +1991,23 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1936
1991
|
if (config.onDetection) {
|
|
1937
1992
|
await config.onDetection(result, context);
|
|
1938
1993
|
}
|
|
1939
|
-
switch (
|
|
1994
|
+
switch (decision.action) {
|
|
1940
1995
|
case "block": {
|
|
1941
1996
|
const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {};
|
|
1942
1997
|
const response2 = server.NextResponse.json(
|
|
1943
1998
|
{ error: message, detected: true, confidence: finalConfidence },
|
|
1944
1999
|
{ status }
|
|
1945
2000
|
);
|
|
1946
|
-
response2.headers.set("
|
|
1947
|
-
response2.headers.set(
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
);
|
|
1951
|
-
response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1952
|
-
response2.headers.set("x-agentshield-verification", verificationMethod);
|
|
2001
|
+
response2.headers.set("kya-detected", "true");
|
|
2002
|
+
response2.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
|
|
2003
|
+
response2.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
|
|
2004
|
+
response2.headers.set("kya-verification", verificationMethod);
|
|
1953
2005
|
return response2;
|
|
1954
2006
|
}
|
|
1955
2007
|
case "log": {
|
|
1956
2008
|
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
1957
2009
|
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
1958
|
-
console.debug(`[AgentShield]
|
|
2010
|
+
console.debug(`[AgentShield] AI Agent detected (${verificationMethod}):`, {
|
|
1959
2011
|
agent: result.detectedAgent?.name,
|
|
1960
2012
|
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
1961
2013
|
path: pathWithQuery,
|
|
@@ -1968,14 +2020,14 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1968
2020
|
}
|
|
1969
2021
|
const response = server.NextResponse.next();
|
|
1970
2022
|
if (result.isAgent) {
|
|
1971
|
-
response.headers.set("
|
|
1972
|
-
response.headers.set("
|
|
1973
|
-
response.headers.set("
|
|
1974
|
-
response.headers.set("
|
|
2023
|
+
response.headers.set("kya-detected", "true");
|
|
2024
|
+
response.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
|
|
2025
|
+
response.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
|
|
2026
|
+
response.headers.set("kya-verification", verificationMethod);
|
|
1975
2027
|
if (finalConfidence > 0.9) {
|
|
1976
|
-
response.headers.set("
|
|
1977
|
-
response.headers.set("
|
|
1978
|
-
response.headers.set("
|
|
2028
|
+
response.headers.set("kya-ai-visitor", "true");
|
|
2029
|
+
response.headers.set("kya-ai-confidence", finalConfidence.toString());
|
|
2030
|
+
response.headers.set("kya-ai-verification", verificationMethod);
|
|
1979
2031
|
}
|
|
1980
2032
|
}
|
|
1981
2033
|
return response;
|
|
@@ -2174,6 +2226,80 @@ function getAgentShieldClient(config) {
|
|
|
2174
2226
|
function resetAgentShieldClient() {
|
|
2175
2227
|
clientInstance = null;
|
|
2176
2228
|
}
|
|
2229
|
+
var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
|
|
2230
|
+
var DEFAULT_CONNECT_PATH = "/connect";
|
|
2231
|
+
function buildAgentInstructionResponse(request, decision, redirectUrl) {
|
|
2232
|
+
const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
|
|
2233
|
+
const agentName = decision.agentName || decision.agentType || "unknown";
|
|
2234
|
+
if (!resolved.searchParams.has("agent")) {
|
|
2235
|
+
resolved.searchParams.set("agent", agentName.toLowerCase());
|
|
2236
|
+
}
|
|
2237
|
+
const authUrl = resolved.toString();
|
|
2238
|
+
const hostname = safeHostname(request.url);
|
|
2239
|
+
const body = {
|
|
2240
|
+
// Markdown-formatted so clients that render markdown (Claude Desktop,
|
|
2241
|
+
// ChatGPT web) surface the URL as a clickable link. Tone mirrors the
|
|
2242
|
+
// gateway response so messaging stays consistent across platforms.
|
|
2243
|
+
message: `I can't access ${hostname} yet \u2014 this site checks AI assistants at the front door.
|
|
2244
|
+
|
|
2245
|
+
**To give me access, open this link:**
|
|
2246
|
+
[Connect securely to ${hostname}](${authUrl})
|
|
2247
|
+
|
|
2248
|
+
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.`,
|
|
2249
|
+
user_action_required: {
|
|
2250
|
+
action: `Connect securely to ${hostname}`,
|
|
2251
|
+
url: authUrl,
|
|
2252
|
+
reason: `${hostname} checks AI assistants before they connect. Open the link to give your assistant a verified key.`
|
|
2253
|
+
},
|
|
2254
|
+
mcp_i: {
|
|
2255
|
+
version: "1.0",
|
|
2256
|
+
action: "authenticate",
|
|
2257
|
+
authorization_url: authUrl,
|
|
2258
|
+
flow: {
|
|
2259
|
+
type: "oauth2_delegation",
|
|
2260
|
+
steps: [
|
|
2261
|
+
"1. Direct your user to the authorization_url",
|
|
2262
|
+
"2. User reviews requested scopes and grants consent",
|
|
2263
|
+
"3. Receive delegation credential (JWT)",
|
|
2264
|
+
"4. Include credential in KYA-Delegation header",
|
|
2265
|
+
"5. Retry this request with the proof"
|
|
2266
|
+
]
|
|
2267
|
+
},
|
|
2268
|
+
retry_instructions: {
|
|
2269
|
+
header: "KYA-Delegation",
|
|
2270
|
+
format: "JWT delegation credential from authorization flow"
|
|
2271
|
+
},
|
|
2272
|
+
documentation: MCP_I_DOCS_URL
|
|
2273
|
+
},
|
|
2274
|
+
error: "mcp_authentication_required",
|
|
2275
|
+
code: "AGENT_REQUIRES_DELEGATION",
|
|
2276
|
+
detection: {
|
|
2277
|
+
agent_type: decision.agentType || "ai_agent",
|
|
2278
|
+
agent_name: decision.agentName || "Unknown Agent",
|
|
2279
|
+
confidence: decision.confidence
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
const response = server.NextResponse.json(body, { status: 401 });
|
|
2283
|
+
response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
|
|
2284
|
+
response.headers.set(
|
|
2285
|
+
"Link",
|
|
2286
|
+
`<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
|
|
2287
|
+
);
|
|
2288
|
+
response.headers.set("KYA-Auth-Required", "true");
|
|
2289
|
+
response.headers.set("KYA-Auth-Url", authUrl);
|
|
2290
|
+
response.headers.set("KYA-Action", "instruct");
|
|
2291
|
+
response.headers.set("KYA-Detected-Agent", agentName);
|
|
2292
|
+
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2293
|
+
response.headers.set("Cache-Control", "no-store");
|
|
2294
|
+
return response;
|
|
2295
|
+
}
|
|
2296
|
+
function resolveUrl(target, baseUrl2) {
|
|
2297
|
+
try {
|
|
2298
|
+
return new URL(target, baseUrl2);
|
|
2299
|
+
} catch {
|
|
2300
|
+
return new URL(DEFAULT_CONNECT_PATH, baseUrl2);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2177
2303
|
|
|
2178
2304
|
// src/api-middleware.ts
|
|
2179
2305
|
function matchPath(path, pattern) {
|
|
@@ -2194,27 +2320,52 @@ function shouldIncludePath(path, includePaths) {
|
|
|
2194
2320
|
if (!includePaths || includePaths.length === 0) return true;
|
|
2195
2321
|
return includePaths.some((pattern) => matchPath(path, pattern));
|
|
2196
2322
|
}
|
|
2197
|
-
function buildBlockedResponse(decision, config) {
|
|
2323
|
+
function buildBlockedResponse(request, decision, config) {
|
|
2198
2324
|
const status = config.blockedResponse?.status ?? 403;
|
|
2199
2325
|
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2326
|
+
const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
|
|
2327
|
+
const body = {
|
|
2328
|
+
error: message,
|
|
2329
|
+
code: "AGENT_BLOCKED",
|
|
2330
|
+
reason: decision.reason,
|
|
2331
|
+
agentType: decision.agentType
|
|
2332
|
+
};
|
|
2333
|
+
if (recoveryUrl) {
|
|
2334
|
+
const hostname = safeHostname(request.url);
|
|
2335
|
+
body.user_action_required = {
|
|
2336
|
+
action: `Connect securely to ${hostname}`,
|
|
2337
|
+
url: recoveryUrl,
|
|
2338
|
+
reason: `${hostname} blocks unverified AI assistants. Open the link to give your assistant a verified key and try again.`
|
|
2339
|
+
};
|
|
2340
|
+
body.message = `I can't access ${hostname} \u2014 this site blocks unverified AI assistants.
|
|
2341
|
+
|
|
2342
|
+
**To give me access, open this link:**
|
|
2343
|
+
[Connect securely to ${hostname}](${recoveryUrl})
|
|
2344
|
+
|
|
2345
|
+
Once you're done, ask me to try again.`;
|
|
2346
|
+
}
|
|
2347
|
+
const response = server.NextResponse.json(body, { status });
|
|
2209
2348
|
if (config.blockedResponse?.headers) {
|
|
2210
2349
|
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2211
2350
|
response.headers.set(key, value);
|
|
2212
2351
|
}
|
|
2213
2352
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2353
|
+
if (recoveryUrl) {
|
|
2354
|
+
response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
|
|
2355
|
+
response.headers.set("KYA-Auth-Url", recoveryUrl);
|
|
2356
|
+
}
|
|
2357
|
+
response.headers.set("KYA-Action", decision.action);
|
|
2358
|
+
response.headers.set("KYA-Reason", decision.reason);
|
|
2216
2359
|
return response;
|
|
2217
2360
|
}
|
|
2361
|
+
function resolveRecoveryUrl(request, target) {
|
|
2362
|
+
if (!target) return void 0;
|
|
2363
|
+
try {
|
|
2364
|
+
return new URL(target, request.url).toString();
|
|
2365
|
+
} catch {
|
|
2366
|
+
return void 0;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2218
2369
|
function buildRedirectResponse(request, decision, config) {
|
|
2219
2370
|
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2220
2371
|
const url = new URL(redirectUrl, request.url);
|
|
@@ -2259,7 +2410,7 @@ function withAgentShield(config = {}) {
|
|
|
2259
2410
|
try {
|
|
2260
2411
|
const client2 = getClient();
|
|
2261
2412
|
const userAgent = request.headers.get("user-agent") || void 0;
|
|
2262
|
-
const ipAddress =
|
|
2413
|
+
const ipAddress = getClientIp(request);
|
|
2263
2414
|
const result = await client2.enforce({
|
|
2264
2415
|
headers: Object.fromEntries(request.headers.entries()),
|
|
2265
2416
|
userAgent,
|
|
@@ -2310,6 +2461,7 @@ function withAgentShield(config = {}) {
|
|
|
2310
2461
|
if (decision.isAgent && config.onAgentDetected) {
|
|
2311
2462
|
await config.onAgentDetected(request, decision);
|
|
2312
2463
|
}
|
|
2464
|
+
const redirectMode = config.redirectMode ?? "instruct";
|
|
2313
2465
|
switch (decision.action) {
|
|
2314
2466
|
case "block": {
|
|
2315
2467
|
if (config.customBlockedResponse) {
|
|
@@ -2318,10 +2470,15 @@ function withAgentShield(config = {}) {
|
|
|
2318
2470
|
if (config.onBlock === "redirect") {
|
|
2319
2471
|
return buildRedirectResponse(request, decision, config);
|
|
2320
2472
|
}
|
|
2321
|
-
return buildBlockedResponse(decision, config);
|
|
2473
|
+
return buildBlockedResponse(request, decision, config);
|
|
2322
2474
|
}
|
|
2323
|
-
case "redirect":
|
|
2324
|
-
|
|
2475
|
+
case "redirect":
|
|
2476
|
+
case "instruct": {
|
|
2477
|
+
if (redirectMode === "http" && decision.action === "redirect") {
|
|
2478
|
+
return buildRedirectResponse(request, decision, config);
|
|
2479
|
+
}
|
|
2480
|
+
const targetUrl = config.redirectUrl || decision.redirectUrl;
|
|
2481
|
+
return buildAgentInstructionResponse(request, decision, targetUrl);
|
|
2325
2482
|
}
|
|
2326
2483
|
case "challenge": {
|
|
2327
2484
|
return buildRedirectResponse(request, decision, config);
|
|
@@ -2331,10 +2488,10 @@ function withAgentShield(config = {}) {
|
|
|
2331
2488
|
default: {
|
|
2332
2489
|
const response = server.NextResponse.next();
|
|
2333
2490
|
if (decision.isAgent) {
|
|
2334
|
-
response.headers.set("
|
|
2335
|
-
response.headers.set("
|
|
2491
|
+
response.headers.set("KYA-Detected", "true");
|
|
2492
|
+
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2336
2493
|
if (decision.agentName) {
|
|
2337
|
-
response.headers.set("
|
|
2494
|
+
response.headers.set("KYA-Agent", decision.agentName);
|
|
2338
2495
|
}
|
|
2339
2496
|
}
|
|
2340
2497
|
return response;
|
|
@@ -2392,24 +2549,28 @@ function buildBlockedResponse2(decision, config) {
|
|
|
2392
2549
|
response.headers.set(key, value);
|
|
2393
2550
|
}
|
|
2394
2551
|
}
|
|
2395
|
-
response.headers.set("
|
|
2396
|
-
response.headers.set("
|
|
2397
|
-
response.headers.set("
|
|
2552
|
+
response.headers.set("KYA-Action", decision.action);
|
|
2553
|
+
response.headers.set("KYA-Reason", decision.reason);
|
|
2554
|
+
response.headers.set("KYA-Match-Type", decision.matchType);
|
|
2398
2555
|
return response;
|
|
2399
2556
|
}
|
|
2400
|
-
function buildRedirectResponse2(request, decision, config) {
|
|
2557
|
+
function buildRedirectResponse2(request, decision, config, detection) {
|
|
2401
2558
|
const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
|
|
2402
2559
|
const url = new URL(redirectUrl, request.url);
|
|
2403
2560
|
url.searchParams.set("reason", decision.reason);
|
|
2404
2561
|
if (decision.ruleId) {
|
|
2405
2562
|
url.searchParams.set("ruleId", decision.ruleId);
|
|
2406
2563
|
}
|
|
2564
|
+
const agentName = detection?.detectedAgent?.name;
|
|
2565
|
+
if (agentName && !url.searchParams.has("agent")) {
|
|
2566
|
+
url.searchParams.set("agent", agentName.toLowerCase());
|
|
2567
|
+
}
|
|
2407
2568
|
return server.NextResponse.redirect(url);
|
|
2408
2569
|
}
|
|
2409
|
-
function buildChallengeResponse(request, decision, config) {
|
|
2410
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2570
|
+
function buildChallengeResponse(request, decision, config, detection) {
|
|
2571
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2411
2572
|
}
|
|
2412
|
-
async function handlePolicyDecision(request, decision, config) {
|
|
2573
|
+
async function handlePolicyDecision(request, decision, config, detection) {
|
|
2413
2574
|
switch (decision.action) {
|
|
2414
2575
|
case agentshieldShared.ENFORCEMENT_ACTIONS.BLOCK:
|
|
2415
2576
|
if (config.customBlockedResponse) {
|
|
@@ -2417,9 +2578,9 @@ async function handlePolicyDecision(request, decision, config) {
|
|
|
2417
2578
|
}
|
|
2418
2579
|
return buildBlockedResponse2(decision, config);
|
|
2419
2580
|
case agentshieldShared.ENFORCEMENT_ACTIONS.REDIRECT:
|
|
2420
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2581
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2421
2582
|
case agentshieldShared.ENFORCEMENT_ACTIONS.CHALLENGE:
|
|
2422
|
-
return buildChallengeResponse(request, decision, config);
|
|
2583
|
+
return buildChallengeResponse(request, decision, config, detection);
|
|
2423
2584
|
case agentshieldShared.ENFORCEMENT_ACTIONS.LOG:
|
|
2424
2585
|
console.log("[AgentShield] Policy decision (log):", {
|
|
2425
2586
|
path: request.nextUrl.pathname,
|
|
@@ -2493,7 +2654,7 @@ async function applyPolicy(request, detection, config) {
|
|
|
2493
2654
|
if (config.onPolicyDecision) {
|
|
2494
2655
|
await config.onPolicyDecision(request, decision, context);
|
|
2495
2656
|
}
|
|
2496
|
-
return await handlePolicyDecision(request, decision, config);
|
|
2657
|
+
return await handlePolicyDecision(request, decision, config, detection);
|
|
2497
2658
|
} catch (error) {
|
|
2498
2659
|
if (config.debug) {
|
|
2499
2660
|
console.error("[AgentShield] Policy evaluation error:", error);
|