@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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadRulesSync,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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["
|
|
1183
|
-
const confidence = headers["
|
|
1184
|
-
const sessionId = headers["
|
|
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("
|
|
1215
|
-
response.setHeader(
|
|
1216
|
-
|
|
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("
|
|
1222
|
-
response.headers.set(
|
|
1223
|
-
|
|
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/
|
|
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
|
-
|
|
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("
|
|
1275
|
-
response2.headers.set("
|
|
1276
|
-
response2.headers.set("
|
|
1277
|
-
response2.headers.set("
|
|
1278
|
-
response2.headers.set("
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1321
|
-
|
|
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 (
|
|
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
|
-
|
|
1390
|
+
activeDetector.emit("agent.blocked", result, context);
|
|
1345
1391
|
return response2;
|
|
1346
1392
|
}
|
|
1347
|
-
case "redirect":
|
|
1348
|
-
|
|
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
|
-
|
|
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("
|
|
1376
|
-
response.headers.set("
|
|
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("
|
|
1428
|
+
response.headers.set("kya-agent", result.detectedAgent.name);
|
|
1379
1429
|
}
|
|
1380
|
-
if (sessionTracker &&
|
|
1430
|
+
if (sessionTracker && decision.shouldNotify) {
|
|
1381
1431
|
response = await sessionTracker.track(request, response, result);
|
|
1382
|
-
response.headers.set("
|
|
1383
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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("
|
|
1926
|
-
response2.headers.set(
|
|
1927
|
-
|
|
1928
|
-
|
|
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]
|
|
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("
|
|
1951
|
-
response.headers.set("
|
|
1952
|
-
response.headers.set("
|
|
1953
|
-
response.headers.set("
|
|
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("
|
|
1956
|
-
response.headers.set("
|
|
1957
|
-
response.headers.set("
|
|
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;
|
|
@@ -1963,7 +2015,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1963
2015
|
|
|
1964
2016
|
// src/api-client.ts
|
|
1965
2017
|
var DEFAULT_BASE_URL = "https://kya.vouched.id";
|
|
1966
|
-
var EDGE_DETECT_URL = "https://detect.
|
|
2018
|
+
var EDGE_DETECT_URL = "https://detect.checkpoint-gateway.ai";
|
|
1967
2019
|
var DEFAULT_TIMEOUT = 5e3;
|
|
1968
2020
|
var AgentShieldClient = class {
|
|
1969
2021
|
apiKey;
|
|
@@ -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,35 +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
|
-
const
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2182
|
-
const responseBody = {
|
|
2183
|
-
error: errorMessage,
|
|
2304
|
+
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2305
|
+
const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
|
|
2306
|
+
const body = {
|
|
2307
|
+
error: message,
|
|
2184
2308
|
code: "AGENT_BLOCKED",
|
|
2185
2309
|
reason: decision.reason,
|
|
2186
|
-
agentType: decision.agentType
|
|
2187
|
-
message
|
|
2310
|
+
agentType: decision.agentType
|
|
2188
2311
|
};
|
|
2189
|
-
if (
|
|
2190
|
-
|
|
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.`;
|
|
2191
2325
|
}
|
|
2192
|
-
const response = NextResponse.json(
|
|
2326
|
+
const response = NextResponse.json(body, { status });
|
|
2193
2327
|
if (config.blockedResponse?.headers) {
|
|
2194
2328
|
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2195
2329
|
response.headers.set(key, value);
|
|
2196
2330
|
}
|
|
2197
2331
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
response.headers.set("X-AgentShield-Redirect", redirectUrl);
|
|
2332
|
+
if (recoveryUrl) {
|
|
2333
|
+
response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
|
|
2334
|
+
response.headers.set("KYA-Auth-Url", recoveryUrl);
|
|
2202
2335
|
}
|
|
2336
|
+
response.headers.set("KYA-Action", decision.action);
|
|
2337
|
+
response.headers.set("KYA-Reason", decision.reason);
|
|
2203
2338
|
return response;
|
|
2204
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
|
+
}
|
|
2205
2348
|
function buildRedirectResponse(request, decision, config) {
|
|
2206
2349
|
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2207
2350
|
const url = new URL(redirectUrl, request.url);
|
|
@@ -2246,7 +2389,7 @@ function withAgentShield(config = {}) {
|
|
|
2246
2389
|
try {
|
|
2247
2390
|
const client2 = getClient();
|
|
2248
2391
|
const userAgent = request.headers.get("user-agent") || void 0;
|
|
2249
|
-
const ipAddress =
|
|
2392
|
+
const ipAddress = getClientIp(request);
|
|
2250
2393
|
const result = await client2.enforce({
|
|
2251
2394
|
headers: Object.fromEntries(request.headers.entries()),
|
|
2252
2395
|
userAgent,
|
|
@@ -2297,6 +2440,7 @@ function withAgentShield(config = {}) {
|
|
|
2297
2440
|
if (decision.isAgent && config.onAgentDetected) {
|
|
2298
2441
|
await config.onAgentDetected(request, decision);
|
|
2299
2442
|
}
|
|
2443
|
+
const redirectMode = config.redirectMode ?? "instruct";
|
|
2300
2444
|
switch (decision.action) {
|
|
2301
2445
|
case "block": {
|
|
2302
2446
|
if (config.customBlockedResponse) {
|
|
@@ -2305,10 +2449,15 @@ function withAgentShield(config = {}) {
|
|
|
2305
2449
|
if (config.onBlock === "redirect") {
|
|
2306
2450
|
return buildRedirectResponse(request, decision, config);
|
|
2307
2451
|
}
|
|
2308
|
-
return buildBlockedResponse(decision, config);
|
|
2452
|
+
return buildBlockedResponse(request, decision, config);
|
|
2309
2453
|
}
|
|
2310
|
-
case "redirect":
|
|
2311
|
-
|
|
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);
|
|
2312
2461
|
}
|
|
2313
2462
|
case "challenge": {
|
|
2314
2463
|
return buildRedirectResponse(request, decision, config);
|
|
@@ -2318,10 +2467,10 @@ function withAgentShield(config = {}) {
|
|
|
2318
2467
|
default: {
|
|
2319
2468
|
const response = NextResponse.next();
|
|
2320
2469
|
if (decision.isAgent) {
|
|
2321
|
-
response.headers.set("
|
|
2322
|
-
response.headers.set("
|
|
2470
|
+
response.headers.set("KYA-Detected", "true");
|
|
2471
|
+
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2323
2472
|
if (decision.agentName) {
|
|
2324
|
-
response.headers.set("
|
|
2473
|
+
response.headers.set("KYA-Agent", decision.agentName);
|
|
2325
2474
|
}
|
|
2326
2475
|
}
|
|
2327
2476
|
return response;
|
|
@@ -2379,24 +2528,28 @@ function buildBlockedResponse2(decision, config) {
|
|
|
2379
2528
|
response.headers.set(key, value);
|
|
2380
2529
|
}
|
|
2381
2530
|
}
|
|
2382
|
-
response.headers.set("
|
|
2383
|
-
response.headers.set("
|
|
2384
|
-
response.headers.set("
|
|
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);
|
|
2385
2534
|
return response;
|
|
2386
2535
|
}
|
|
2387
|
-
function buildRedirectResponse2(request, decision, config) {
|
|
2536
|
+
function buildRedirectResponse2(request, decision, config, detection) {
|
|
2388
2537
|
const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
|
|
2389
2538
|
const url = new URL(redirectUrl, request.url);
|
|
2390
2539
|
url.searchParams.set("reason", decision.reason);
|
|
2391
2540
|
if (decision.ruleId) {
|
|
2392
2541
|
url.searchParams.set("ruleId", decision.ruleId);
|
|
2393
2542
|
}
|
|
2543
|
+
const agentName = detection?.detectedAgent?.name;
|
|
2544
|
+
if (agentName && !url.searchParams.has("agent")) {
|
|
2545
|
+
url.searchParams.set("agent", agentName.toLowerCase());
|
|
2546
|
+
}
|
|
2394
2547
|
return NextResponse.redirect(url);
|
|
2395
2548
|
}
|
|
2396
|
-
function buildChallengeResponse(request, decision, config) {
|
|
2397
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2549
|
+
function buildChallengeResponse(request, decision, config, detection) {
|
|
2550
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2398
2551
|
}
|
|
2399
|
-
async function handlePolicyDecision(request, decision, config) {
|
|
2552
|
+
async function handlePolicyDecision(request, decision, config, detection) {
|
|
2400
2553
|
switch (decision.action) {
|
|
2401
2554
|
case ENFORCEMENT_ACTIONS.BLOCK:
|
|
2402
2555
|
if (config.customBlockedResponse) {
|
|
@@ -2404,9 +2557,9 @@ async function handlePolicyDecision(request, decision, config) {
|
|
|
2404
2557
|
}
|
|
2405
2558
|
return buildBlockedResponse2(decision, config);
|
|
2406
2559
|
case ENFORCEMENT_ACTIONS.REDIRECT:
|
|
2407
|
-
return buildRedirectResponse2(request, decision, config);
|
|
2560
|
+
return buildRedirectResponse2(request, decision, config, detection);
|
|
2408
2561
|
case ENFORCEMENT_ACTIONS.CHALLENGE:
|
|
2409
|
-
return buildChallengeResponse(request, decision, config);
|
|
2562
|
+
return buildChallengeResponse(request, decision, config, detection);
|
|
2410
2563
|
case ENFORCEMENT_ACTIONS.LOG:
|
|
2411
2564
|
console.log("[AgentShield] Policy decision (log):", {
|
|
2412
2565
|
path: request.nextUrl.pathname,
|
|
@@ -2480,7 +2633,7 @@ async function applyPolicy(request, detection, config) {
|
|
|
2480
2633
|
if (config.onPolicyDecision) {
|
|
2481
2634
|
await config.onPolicyDecision(request, decision, context);
|
|
2482
2635
|
}
|
|
2483
|
-
return await handlePolicyDecision(request, decision, config);
|
|
2636
|
+
return await handlePolicyDecision(request, decision, config, detection);
|
|
2484
2637
|
} catch (error) {
|
|
2485
2638
|
if (config.debug) {
|
|
2486
2639
|
console.error("[AgentShield] Policy evaluation error:", error);
|