@kya-os/agentshield-nextjs 0.2.12 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/api-client.d.mts +6 -1
- package/dist/api-client.d.ts +6 -1
- package/dist/api-client.js +1 -1
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.mjs +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 +146 -24
- package/dist/api-middleware.js.map +1 -1
- package/dist/api-middleware.mjs +146 -24
- 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 +260 -107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +261 -108
- 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 +5 -5
- package/wasm/agentshield_wasm.d.ts +2 -2
- 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;
|
|
@@ -1984,7 +2036,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1984
2036
|
|
|
1985
2037
|
// src/api-client.ts
|
|
1986
2038
|
var DEFAULT_BASE_URL = "https://kya.vouched.id";
|
|
1987
|
-
var EDGE_DETECT_URL = "https://detect.
|
|
2039
|
+
var EDGE_DETECT_URL = "https://detect.checkpoint-gateway.ai";
|
|
1988
2040
|
var DEFAULT_TIMEOUT = 5e3;
|
|
1989
2041
|
var AgentShieldClient = class {
|
|
1990
2042
|
apiKey;
|
|
@@ -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,35 +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
|
-
const
|
|
2200
|
-
const
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
const responseBody = {
|
|
2204
|
-
error: errorMessage,
|
|
2325
|
+
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2326
|
+
const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
|
|
2327
|
+
const body = {
|
|
2328
|
+
error: message,
|
|
2205
2329
|
code: "AGENT_BLOCKED",
|
|
2206
2330
|
reason: decision.reason,
|
|
2207
|
-
agentType: decision.agentType
|
|
2208
|
-
message
|
|
2331
|
+
agentType: decision.agentType
|
|
2209
2332
|
};
|
|
2210
|
-
if (
|
|
2211
|
-
|
|
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.`;
|
|
2212
2346
|
}
|
|
2213
|
-
const response = server.NextResponse.json(
|
|
2347
|
+
const response = server.NextResponse.json(body, { status });
|
|
2214
2348
|
if (config.blockedResponse?.headers) {
|
|
2215
2349
|
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2216
2350
|
response.headers.set(key, value);
|
|
2217
2351
|
}
|
|
2218
2352
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
response.headers.set("X-AgentShield-Redirect", redirectUrl);
|
|
2353
|
+
if (recoveryUrl) {
|
|
2354
|
+
response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
|
|
2355
|
+
response.headers.set("KYA-Auth-Url", recoveryUrl);
|
|
2223
2356
|
}
|
|
2357
|
+
response.headers.set("KYA-Action", decision.action);
|
|
2358
|
+
response.headers.set("KYA-Reason", decision.reason);
|
|
2224
2359
|
return response;
|
|
2225
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
|
+
}
|
|
2226
2369
|
function buildRedirectResponse(request, decision, config) {
|
|
2227
2370
|
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2228
2371
|
const url = new URL(redirectUrl, request.url);
|
|
@@ -2267,7 +2410,7 @@ function withAgentShield(config = {}) {
|
|
|
2267
2410
|
try {
|
|
2268
2411
|
const client2 = getClient();
|
|
2269
2412
|
const userAgent = request.headers.get("user-agent") || void 0;
|
|
2270
|
-
const ipAddress =
|
|
2413
|
+
const ipAddress = getClientIp(request);
|
|
2271
2414
|
const result = await client2.enforce({
|
|
2272
2415
|
headers: Object.fromEntries(request.headers.entries()),
|
|
2273
2416
|
userAgent,
|
|
@@ -2318,6 +2461,7 @@ function withAgentShield(config = {}) {
|
|
|
2318
2461
|
if (decision.isAgent && config.onAgentDetected) {
|
|
2319
2462
|
await config.onAgentDetected(request, decision);
|
|
2320
2463
|
}
|
|
2464
|
+
const redirectMode = config.redirectMode ?? "instruct";
|
|
2321
2465
|
switch (decision.action) {
|
|
2322
2466
|
case "block": {
|
|
2323
2467
|
if (config.customBlockedResponse) {
|
|
@@ -2326,10 +2470,15 @@ function withAgentShield(config = {}) {
|
|
|
2326
2470
|
if (config.onBlock === "redirect") {
|
|
2327
2471
|
return buildRedirectResponse(request, decision, config);
|
|
2328
2472
|
}
|
|
2329
|
-
return buildBlockedResponse(decision, config);
|
|
2473
|
+
return buildBlockedResponse(request, decision, config);
|
|
2330
2474
|
}
|
|
2331
|
-
case "redirect":
|
|
2332
|
-
|
|
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);
|
|
2333
2482
|
}
|
|
2334
2483
|
case "challenge": {
|
|
2335
2484
|
return buildRedirectResponse(request, decision, config);
|
|
@@ -2339,10 +2488,10 @@ function withAgentShield(config = {}) {
|
|
|
2339
2488
|
default: {
|
|
2340
2489
|
const response = server.NextResponse.next();
|
|
2341
2490
|
if (decision.isAgent) {
|
|
2342
|
-
response.headers.set("
|
|
2343
|
-
response.headers.set("
|
|
2491
|
+
response.headers.set("KYA-Detected", "true");
|
|
2492
|
+
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2344
2493
|
if (decision.agentName) {
|
|
2345
|
-
response.headers.set("
|
|
2494
|
+
response.headers.set("KYA-Agent", decision.agentName);
|
|
2346
2495
|
}
|
|
2347
2496
|
}
|
|
2348
2497
|
return response;
|
|
@@ -2400,24 +2549,28 @@ function buildBlockedResponse2(decision, config) {
|
|
|
2400
2549
|
response.headers.set(key, value);
|
|
2401
2550
|
}
|
|
2402
2551
|
}
|
|
2403
|
-
response.headers.set("
|
|
2404
|
-
response.headers.set("
|
|
2405
|
-
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);
|
|
2406
2555
|
return response;
|
|
2407
2556
|
}
|
|
2408
|
-
function buildRedirectResponse2(request, decision, config) {
|
|
2557
|
+
function buildRedirectResponse2(request, decision, config, detection) {
|
|
2409
2558
|
const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
|
|
2410
2559
|
const url = new URL(redirectUrl, request.url);
|
|
2411
2560
|
url.searchParams.set("reason", decision.reason);
|
|
2412
2561
|
if (decision.ruleId) {
|
|
2413
2562
|
url.searchParams.set("ruleId", decision.ruleId);
|
|
2414
2563
|
}
|
|
2564
|
+
const agentName = detection?.detectedAgent?.name;
|
|
2565
|
+
if (agentName && !url.searchParams.has("agent")) {
|
|
2566
|
+
url.searchParams.set("agent", agentName.toLowerCase());
|
|
2567
|
+
}
|
|
2415
2568
|
return server.NextResponse.redirect(url);
|
|
2416
2569
|
}
|
|
2417
|
-
function buildChallengeResponse(request, decision, config) {
|
|
2418
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2570
|
+
function buildChallengeResponse(request, decision, config, detection) {
|
|
2571
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2419
2572
|
}
|
|
2420
|
-
async function handlePolicyDecision(request, decision, config) {
|
|
2573
|
+
async function handlePolicyDecision(request, decision, config, detection) {
|
|
2421
2574
|
switch (decision.action) {
|
|
2422
2575
|
case agentshieldShared.ENFORCEMENT_ACTIONS.BLOCK:
|
|
2423
2576
|
if (config.customBlockedResponse) {
|
|
@@ -2425,9 +2578,9 @@ async function handlePolicyDecision(request, decision, config) {
|
|
|
2425
2578
|
}
|
|
2426
2579
|
return buildBlockedResponse2(decision, config);
|
|
2427
2580
|
case agentshieldShared.ENFORCEMENT_ACTIONS.REDIRECT:
|
|
2428
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2581
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2429
2582
|
case agentshieldShared.ENFORCEMENT_ACTIONS.CHALLENGE:
|
|
2430
|
-
return buildChallengeResponse(request, decision, config);
|
|
2583
|
+
return buildChallengeResponse(request, decision, config, detection);
|
|
2431
2584
|
case agentshieldShared.ENFORCEMENT_ACTIONS.LOG:
|
|
2432
2585
|
console.log("[AgentShield] Policy decision (log):", {
|
|
2433
2586
|
path: request.nextUrl.pathname,
|
|
@@ -2501,7 +2654,7 @@ async function applyPolicy(request, detection, config) {
|
|
|
2501
2654
|
if (config.onPolicyDecision) {
|
|
2502
2655
|
await config.onPolicyDecision(request, decision, context);
|
|
2503
2656
|
}
|
|
2504
|
-
return await handlePolicyDecision(request, decision, config);
|
|
2657
|
+
return await handlePolicyDecision(request, decision, config, detection);
|
|
2505
2658
|
} catch (error) {
|
|
2506
2659
|
if (config.debug) {
|
|
2507
2660
|
console.error("[AgentShield] Policy evaluation error:", error);
|