@kya-os/agentshield-nextjs 0.1.32 → 0.1.34

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/index.js CHANGED
@@ -1057,6 +1057,466 @@ function createAgentShieldMiddleware2(config) {
1057
1057
  };
1058
1058
  }
1059
1059
 
1060
+ // src/wasm-confidence.ts
1061
+ async function checkWasmAvailability() {
1062
+ try {
1063
+ if (typeof WebAssembly === "undefined") {
1064
+ return false;
1065
+ }
1066
+ const wasmCode = new Uint8Array([
1067
+ 0,
1068
+ 97,
1069
+ 115,
1070
+ 109,
1071
+ 1,
1072
+ 0,
1073
+ 0,
1074
+ 0
1075
+ ]);
1076
+ const module = await WebAssembly.compile(wasmCode);
1077
+ await WebAssembly.instantiate(module);
1078
+ return true;
1079
+ } catch {
1080
+ return false;
1081
+ }
1082
+ }
1083
+ function shouldIndicateWasmVerification(confidence) {
1084
+ return confidence >= 0.85 && confidence < 1;
1085
+ }
1086
+ function getWasmConfidenceBoost(baseConfidence, reasons = []) {
1087
+ if (reasons.some(
1088
+ (r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
1089
+ )) {
1090
+ return 1;
1091
+ }
1092
+ if (baseConfidence >= 0.85) {
1093
+ return 0.95;
1094
+ }
1095
+ if (baseConfidence >= 0.7) {
1096
+ return Math.min(baseConfidence * 1.1, 0.9);
1097
+ }
1098
+ return baseConfidence;
1099
+ }
1100
+ function getVerificationMethod(confidence, reasons = []) {
1101
+ if (reasons.some(
1102
+ (r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
1103
+ )) {
1104
+ return "signature";
1105
+ }
1106
+ if (shouldIndicateWasmVerification(confidence)) {
1107
+ return "wasm-enhanced";
1108
+ }
1109
+ return "pattern";
1110
+ }
1111
+
1112
+ // src/storage/memory-adapter.ts
1113
+ var MemoryStorageAdapter = class {
1114
+ events = /* @__PURE__ */ new Map();
1115
+ sessions = /* @__PURE__ */ new Map();
1116
+ eventTimeline = [];
1117
+ maxEvents = 1e3;
1118
+ maxSessions = 100;
1119
+ async storeEvent(event) {
1120
+ const eventKey = `${event.timestamp}:${event.eventId}`;
1121
+ this.events.set(eventKey, event);
1122
+ this.eventTimeline.push({
1123
+ timestamp: Date.parse(event.timestamp),
1124
+ eventId: eventKey
1125
+ });
1126
+ this.eventTimeline.sort((a, b) => b.timestamp - a.timestamp);
1127
+ if (this.eventTimeline.length > this.maxEvents) {
1128
+ const removed = this.eventTimeline.splice(this.maxEvents);
1129
+ removed.forEach((item) => this.events.delete(item.eventId));
1130
+ }
1131
+ }
1132
+ async getRecentEvents(limit = 100) {
1133
+ const recent = this.eventTimeline.slice(0, limit);
1134
+ return recent.map((item) => this.events.get(item.eventId)).filter((event) => event !== void 0);
1135
+ }
1136
+ async getSessionEvents(sessionId) {
1137
+ const events = [];
1138
+ for (const event of this.events.values()) {
1139
+ if (event.sessionId === sessionId) {
1140
+ events.push(event);
1141
+ }
1142
+ }
1143
+ return events.sort(
1144
+ (a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)
1145
+ );
1146
+ }
1147
+ async storeSession(session) {
1148
+ this.sessions.set(session.sessionId, session);
1149
+ if (this.sessions.size > this.maxSessions) {
1150
+ const sortedSessions = Array.from(this.sessions.entries()).sort((a, b) => Date.parse(b[1].lastSeen) - Date.parse(a[1].lastSeen));
1151
+ const toRemove = sortedSessions.slice(this.maxSessions);
1152
+ toRemove.forEach(([id]) => this.sessions.delete(id));
1153
+ }
1154
+ }
1155
+ async getSession(sessionId) {
1156
+ return this.sessions.get(sessionId) || null;
1157
+ }
1158
+ async getRecentSessions(limit = 10) {
1159
+ const sorted = Array.from(this.sessions.values()).sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen));
1160
+ return sorted.slice(0, limit);
1161
+ }
1162
+ async cleanup(olderThan) {
1163
+ const cutoff = olderThan.getTime();
1164
+ this.eventTimeline = this.eventTimeline.filter((item) => {
1165
+ if (item.timestamp < cutoff) {
1166
+ this.events.delete(item.eventId);
1167
+ return false;
1168
+ }
1169
+ return true;
1170
+ });
1171
+ for (const [id, session] of this.sessions.entries()) {
1172
+ if (Date.parse(session.lastSeen) < cutoff) {
1173
+ this.sessions.delete(id);
1174
+ }
1175
+ }
1176
+ }
1177
+ };
1178
+
1179
+ // src/storage/redis-adapter.ts
1180
+ var RedisStorageAdapter = class {
1181
+ redis;
1182
+ ttl;
1183
+ keyPrefix = "agent-shield";
1184
+ constructor(redis, ttl = 86400) {
1185
+ this.redis = redis;
1186
+ this.ttl = ttl;
1187
+ }
1188
+ eventKey(timestamp, eventId) {
1189
+ return `${this.keyPrefix}:events:${timestamp}:${eventId}`;
1190
+ }
1191
+ sessionKey(sessionId) {
1192
+ return `${this.keyPrefix}:sessions:${sessionId}`;
1193
+ }
1194
+ timelineKey() {
1195
+ return `${this.keyPrefix}:events:timeline`;
1196
+ }
1197
+ async storeEvent(event) {
1198
+ const key = this.eventKey(event.timestamp, event.eventId);
1199
+ await this.redis.setex(key, this.ttl, JSON.stringify(event));
1200
+ await this.redis.zadd(this.timelineKey(), {
1201
+ score: Date.parse(event.timestamp),
1202
+ member: key
1203
+ });
1204
+ const count = await this.redis.zcard(this.timelineKey());
1205
+ if (count && count > 1e3) {
1206
+ await this.redis.zremrangebyrank(this.timelineKey(), 0, -1001);
1207
+ }
1208
+ }
1209
+ async getRecentEvents(limit = 100) {
1210
+ const keys = await this.redis.zrevrange(this.timelineKey(), 0, limit - 1);
1211
+ if (!keys || keys.length === 0) {
1212
+ return [];
1213
+ }
1214
+ const events = [];
1215
+ for (const key of keys) {
1216
+ const data = await this.redis.get(key);
1217
+ if (data) {
1218
+ try {
1219
+ const event = typeof data === "string" ? JSON.parse(data) : data;
1220
+ events.push(event);
1221
+ } catch (e) {
1222
+ console.error(`Failed to parse event ${key}:`, e);
1223
+ }
1224
+ }
1225
+ }
1226
+ return events;
1227
+ }
1228
+ async getSessionEvents(sessionId) {
1229
+ const keys = await this.redis.zrevrange(this.timelineKey(), 0, -1);
1230
+ if (!keys || keys.length === 0) {
1231
+ return [];
1232
+ }
1233
+ const events = [];
1234
+ for (const key of keys) {
1235
+ const data = await this.redis.get(key);
1236
+ if (data) {
1237
+ try {
1238
+ const event = typeof data === "string" ? JSON.parse(data) : data;
1239
+ if (event.sessionId === sessionId) {
1240
+ events.push(event);
1241
+ }
1242
+ } catch (e) {
1243
+ console.error(`Failed to parse event ${key}:`, e);
1244
+ }
1245
+ }
1246
+ }
1247
+ return events;
1248
+ }
1249
+ async storeSession(session) {
1250
+ const key = this.sessionKey(session.sessionId);
1251
+ const existing = await this.redis.get(key);
1252
+ if (existing) {
1253
+ const existingSession = typeof existing === "string" ? JSON.parse(existing) : existing;
1254
+ const methods = /* @__PURE__ */ new Set([
1255
+ ...existingSession.verificationMethods || [],
1256
+ ...session.verificationMethods
1257
+ ]);
1258
+ const updatedSession = {
1259
+ ...existingSession,
1260
+ lastSeen: session.lastSeen,
1261
+ eventCount: session.eventCount,
1262
+ paths: Array.from(/* @__PURE__ */ new Set([...existingSession.paths, ...session.paths])),
1263
+ averageConfidence: session.averageConfidence,
1264
+ verificationMethods: Array.from(methods)
1265
+ };
1266
+ await this.redis.setex(key, this.ttl, JSON.stringify(updatedSession));
1267
+ } else {
1268
+ await this.redis.setex(key, this.ttl, JSON.stringify(session));
1269
+ }
1270
+ }
1271
+ async getSession(sessionId) {
1272
+ const key = this.sessionKey(sessionId);
1273
+ const data = await this.redis.get(key);
1274
+ if (!data) {
1275
+ return null;
1276
+ }
1277
+ try {
1278
+ return typeof data === "string" ? JSON.parse(data) : data;
1279
+ } catch (e) {
1280
+ console.error(`Failed to parse session ${sessionId}:`, e);
1281
+ return null;
1282
+ }
1283
+ }
1284
+ async getRecentSessions(limit = 10) {
1285
+ const pattern = `${this.keyPrefix}:sessions:*`;
1286
+ const sessions = [];
1287
+ let cursor = 0;
1288
+ do {
1289
+ const [nextCursor, keys] = await this.redis.scan(cursor, {
1290
+ match: pattern,
1291
+ count: 100
1292
+ });
1293
+ cursor = parseInt(nextCursor);
1294
+ for (const key of keys) {
1295
+ const data = await this.redis.get(key);
1296
+ if (data) {
1297
+ try {
1298
+ const session = typeof data === "string" ? JSON.parse(data) : data;
1299
+ sessions.push(session);
1300
+ } catch (e) {
1301
+ console.error(`Failed to parse session from ${key}:`, e);
1302
+ }
1303
+ }
1304
+ }
1305
+ } while (cursor !== 0 && sessions.length < limit * 2);
1306
+ return sessions.sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)).slice(0, limit);
1307
+ }
1308
+ async cleanup(olderThan) {
1309
+ const cutoff = olderThan.getTime();
1310
+ await this.redis.zremrangebyrank(
1311
+ this.timelineKey(),
1312
+ 0,
1313
+ Math.floor(cutoff / 1e3)
1314
+ );
1315
+ }
1316
+ };
1317
+
1318
+ // src/storage/index.ts
1319
+ async function createStorageAdapter(config) {
1320
+ if (!config || config.type === "memory") {
1321
+ return new MemoryStorageAdapter();
1322
+ }
1323
+ if (config.type === "custom" && config.custom) {
1324
+ return config.custom;
1325
+ }
1326
+ if (config.type === "redis" && config.redis) {
1327
+ try {
1328
+ const { Redis } = await import('@upstash/redis');
1329
+ const redis = new Redis({
1330
+ url: config.redis.url,
1331
+ token: config.redis.token
1332
+ });
1333
+ return new RedisStorageAdapter(redis, config.ttl);
1334
+ } catch (error) {
1335
+ console.warn("[AgentShield] Failed to initialize Redis storage, falling back to memory:", error);
1336
+ return new MemoryStorageAdapter();
1337
+ }
1338
+ }
1339
+ return new MemoryStorageAdapter();
1340
+ }
1341
+
1342
+ // src/enhanced-middleware.ts
1343
+ var SessionManager = class {
1344
+ sessionLastActivity = /* @__PURE__ */ new Map();
1345
+ generateSessionId(ipAddress, userAgent) {
1346
+ const now = Date.now();
1347
+ const timeWindow = Math.floor(now / (5 * 60 * 1e3));
1348
+ const baseKey = `${ipAddress || "unknown"}:${userAgent || "unknown"}`;
1349
+ const windowKey = `${baseKey}:${timeWindow}`;
1350
+ const lastActivity = this.sessionLastActivity.get(windowKey);
1351
+ const shouldCreateNewSession = !lastActivity || now - lastActivity > 3e4;
1352
+ this.sessionLastActivity.set(windowKey, now);
1353
+ if (this.sessionLastActivity.size > 100) {
1354
+ const cutoff = now - 6e5;
1355
+ for (const [key, time] of this.sessionLastActivity.entries()) {
1356
+ if (time < cutoff) {
1357
+ this.sessionLastActivity.delete(key);
1358
+ }
1359
+ }
1360
+ }
1361
+ const data = shouldCreateNewSession ? `${windowKey}:${now}` : `${windowKey}:${lastActivity}`;
1362
+ let hash = 0;
1363
+ for (let i = 0; i < data.length; i++) {
1364
+ const char = data.charCodeAt(i);
1365
+ hash = (hash << 5) - hash + char;
1366
+ hash = hash & hash;
1367
+ }
1368
+ return Math.abs(hash).toString(16).padStart(12, "0").substring(0, 12);
1369
+ }
1370
+ };
1371
+ function createEnhancedAgentShieldMiddleware(config = {}) {
1372
+ let storageAdapter = null;
1373
+ let storageInitPromise = null;
1374
+ const getStorage = async () => {
1375
+ if (storageAdapter) return storageAdapter;
1376
+ if (storageInitPromise) return storageInitPromise;
1377
+ storageInitPromise = createStorageAdapter(config.storage).then((adapter) => {
1378
+ storageAdapter = adapter;
1379
+ return adapter;
1380
+ });
1381
+ return storageInitPromise;
1382
+ };
1383
+ let wasmAvailable = false;
1384
+ checkWasmAvailability().then((available) => {
1385
+ wasmAvailable = available;
1386
+ if (available) {
1387
+ console.log("[AgentShield] \u2705 WASM support detected - enhanced detection enabled");
1388
+ }
1389
+ });
1390
+ const detector = wasmAvailable ? new EdgeAgentDetectorWrapperWithWasm({ enableWasm: true }) : new EdgeAgentDetectorWrapper({});
1391
+ const sessionManager = new SessionManager();
1392
+ const sessionTrackingEnabled = config.sessionTracking?.enabled !== false;
1393
+ return async (request) => {
1394
+ const { pathname } = request.nextUrl;
1395
+ if (config.skipPaths?.some((path) => pathname.startsWith(path))) {
1396
+ return server.NextResponse.next();
1397
+ }
1398
+ const userAgent = request.headers.get("user-agent");
1399
+ const ipAddress = request.ip ?? request.headers.get("x-forwarded-for");
1400
+ const url = new URL(request.url);
1401
+ const pathWithQuery = url.pathname + url.search;
1402
+ const context = {
1403
+ userAgent: userAgent || "",
1404
+ ipAddress: ipAddress || "",
1405
+ headers: Object.fromEntries(request.headers.entries()),
1406
+ url: pathWithQuery,
1407
+ method: request.method,
1408
+ timestamp: /* @__PURE__ */ new Date()
1409
+ };
1410
+ const result = await detector.analyze(context);
1411
+ let finalConfidence = result.confidence;
1412
+ let verificationMethod = result.verificationMethod || "pattern";
1413
+ if (result.isAgent) {
1414
+ const reasons = result.reasons || [];
1415
+ if (shouldIndicateWasmVerification(result.confidence)) {
1416
+ finalConfidence = getWasmConfidenceBoost(result.confidence, reasons);
1417
+ verificationMethod = getVerificationMethod(finalConfidence, reasons);
1418
+ }
1419
+ }
1420
+ if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
1421
+ if (sessionTrackingEnabled) {
1422
+ const storage = await getStorage();
1423
+ const sessionId = sessionManager.generateSessionId(ipAddress || void 0, userAgent || void 0);
1424
+ const event = {
1425
+ eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
1426
+ sessionId,
1427
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1428
+ agentType: result.detectedAgent?.type || "unknown",
1429
+ agentName: result.detectedAgent?.name || "Unknown",
1430
+ confidence: finalConfidence,
1431
+ path: pathWithQuery,
1432
+ ...userAgent && { userAgent },
1433
+ ...ipAddress && { ipAddress },
1434
+ method: request.method,
1435
+ detectionReasons: result.reasons || [],
1436
+ verificationMethod,
1437
+ detectionDetails: {
1438
+ patterns: result.detectedAgent?.patterns,
1439
+ behaviors: result.detectedAgent?.behaviors,
1440
+ fingerprintMatches: result.detectedAgent?.fingerprints
1441
+ }
1442
+ };
1443
+ try {
1444
+ await storage.storeEvent(event);
1445
+ let session = await storage.getSession(sessionId);
1446
+ if (session) {
1447
+ session.lastSeen = event.timestamp;
1448
+ session.eventCount++;
1449
+ if (!session.paths.includes(pathWithQuery)) {
1450
+ session.paths.push(pathWithQuery);
1451
+ }
1452
+ session.averageConfidence = (session.averageConfidence * (session.eventCount - 1) + finalConfidence) / session.eventCount;
1453
+ if (!session.verificationMethods.includes(verificationMethod)) {
1454
+ session.verificationMethods.push(verificationMethod);
1455
+ }
1456
+ } else {
1457
+ session = {
1458
+ sessionId,
1459
+ ...ipAddress && { ipAddress },
1460
+ ...userAgent && { userAgent },
1461
+ agentType: result.detectedAgent?.type || "unknown",
1462
+ agentName: result.detectedAgent?.name || "Unknown",
1463
+ firstSeen: event.timestamp,
1464
+ lastSeen: event.timestamp,
1465
+ eventCount: 1,
1466
+ paths: [pathWithQuery],
1467
+ averageConfidence: finalConfidence,
1468
+ verificationMethods: [verificationMethod]
1469
+ };
1470
+ }
1471
+ if (session) {
1472
+ await storage.storeSession(session);
1473
+ }
1474
+ } catch (error) {
1475
+ console.error("[AgentShield] Failed to store event:", error);
1476
+ }
1477
+ }
1478
+ if (config.onDetection) {
1479
+ await config.onDetection(result, context);
1480
+ }
1481
+ switch (config.onAgentDetected) {
1482
+ case "block": {
1483
+ const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {};
1484
+ const response2 = server.NextResponse.json(
1485
+ { error: message, detected: true, confidence: finalConfidence },
1486
+ { status }
1487
+ );
1488
+ response2.headers.set("x-agentshield-detected", "true");
1489
+ response2.headers.set("x-agentshield-confidence", String(Math.round(finalConfidence * 100)));
1490
+ response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
1491
+ response2.headers.set("x-agentshield-verification", verificationMethod);
1492
+ return response2;
1493
+ }
1494
+ case "log":
1495
+ console.log(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
1496
+ agent: result.detectedAgent?.name,
1497
+ confidence: `${(finalConfidence * 100).toFixed(0)}%`,
1498
+ path: pathWithQuery,
1499
+ verification: verificationMethod
1500
+ });
1501
+ break;
1502
+ }
1503
+ }
1504
+ const response = server.NextResponse.next();
1505
+ if (result.isAgent) {
1506
+ response.headers.set("x-agentshield-detected", "true");
1507
+ response.headers.set("x-agentshield-confidence", String(Math.round(finalConfidence * 100)));
1508
+ response.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
1509
+ response.headers.set("x-agentshield-verification", verificationMethod);
1510
+ if (finalConfidence > 0.9) {
1511
+ response.headers.set("x-ai-visitor", "true");
1512
+ response.headers.set("x-ai-confidence", finalConfidence.toString());
1513
+ response.headers.set("x-ai-verification", verificationMethod);
1514
+ }
1515
+ }
1516
+ return response;
1517
+ };
1518
+ }
1519
+
1060
1520
  // src/index.ts
1061
1521
  var VERSION = "0.1.0";
1062
1522
  /**
@@ -1070,6 +1530,7 @@ exports.StatelessSessionChecker = StatelessSessionChecker;
1070
1530
  exports.VERSION = VERSION;
1071
1531
  exports.createAgentShieldMiddleware = createAgentShieldMiddleware2;
1072
1532
  exports.createAgentShieldMiddlewareBase = createAgentShieldMiddleware;
1533
+ exports.createEnhancedAgentShieldMiddleware = createEnhancedAgentShieldMiddleware;
1073
1534
  exports.createMiddleware = createAgentShieldMiddleware2;
1074
1535
  //# sourceMappingURL=index.js.map
1075
1536
  //# sourceMappingURL=index.js.map