@kya-os/agentshield-nextjs 0.1.41 → 0.1.42
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 +145 -0
- package/dist/api-client.d.ts +145 -0
- package/dist/api-client.js +130 -0
- package/dist/api-client.js.map +1 -0
- package/dist/api-client.mjs +126 -0
- package/dist/api-client.mjs.map +1 -0
- package/dist/api-middleware.d.mts +118 -0
- package/dist/api-middleware.d.ts +118 -0
- package/dist/api-middleware.js +295 -0
- package/dist/api-middleware.js.map +1 -0
- package/dist/api-middleware.mjs +292 -0
- package/dist/api-middleware.mjs.map +1 -0
- package/dist/create-middleware.d.mts +2 -1
- package/dist/create-middleware.d.ts +2 -1
- package/dist/create-middleware.js +474 -200
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +454 -200
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge/index.d.mts +110 -0
- package/dist/edge/index.d.ts +110 -0
- package/dist/edge/index.js +253 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/edge/index.mjs +251 -0
- package/dist/edge/index.mjs.map +1 -0
- package/dist/edge-detector-wrapper.d.mts +6 -15
- package/dist/edge-detector-wrapper.d.ts +6 -15
- package/dist/edge-detector-wrapper.js +314 -95
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +294 -95
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/edge-runtime-loader.d.mts +1 -1
- package/dist/edge-runtime-loader.d.ts +1 -1
- package/dist/edge-runtime-loader.js +10 -25
- package/dist/edge-runtime-loader.js.map +1 -1
- package/dist/edge-runtime-loader.mjs +11 -23
- package/dist/edge-runtime-loader.mjs.map +1 -1
- package/dist/edge-wasm-middleware.js +2 -1
- package/dist/edge-wasm-middleware.js.map +1 -1
- package/dist/edge-wasm-middleware.mjs +2 -1
- package/dist/edge-wasm-middleware.mjs.map +1 -1
- package/dist/enhanced-middleware.d.mts +153 -0
- package/dist/enhanced-middleware.d.ts +153 -0
- package/dist/enhanced-middleware.js +1074 -0
- package/dist/enhanced-middleware.js.map +1 -0
- package/dist/enhanced-middleware.mjs +1072 -0
- package/dist/enhanced-middleware.mjs.map +1 -0
- package/dist/index.d.mts +8 -153
- package/dist/index.d.ts +8 -153
- package/dist/index.js +821 -233
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +797 -234
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +2 -1
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.js +474 -200
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +454 -200
- package/dist/middleware.mjs.map +1 -1
- package/dist/session-tracker.d.mts +1 -1
- package/dist/session-tracker.d.ts +1 -1
- package/dist/session-tracker.js.map +1 -1
- package/dist/session-tracker.mjs.map +1 -1
- package/dist/signature-verifier.d.mts +1 -0
- package/dist/signature-verifier.d.ts +1 -0
- package/dist/signature-verifier.js +204 -44
- package/dist/signature-verifier.js.map +1 -1
- package/dist/signature-verifier.mjs +184 -44
- package/dist/signature-verifier.mjs.map +1 -1
- package/dist/{types-BJTEUa4T.d.mts → types-DVmy9NE3.d.mts} +19 -2
- package/dist/{types-BJTEUa4T.d.ts → types-DVmy9NE3.d.ts} +19 -2
- package/dist/wasm-middleware.js +15 -6
- package/dist/wasm-middleware.js.map +1 -1
- package/dist/wasm-middleware.mjs +15 -6
- package/dist/wasm-middleware.mjs.map +1 -1
- package/package.json +27 -6
- package/wasm/agentshield_wasm.js +209 -152
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/wasm/package.json +30 -0
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var agentshieldShared = require('@kya-os/agentshield-shared');
|
|
4
|
+
var server = require('next/server');
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/wasm-confidence.ts
|
|
17
|
+
var wasm_confidence_exports = {};
|
|
18
|
+
__export(wasm_confidence_exports, {
|
|
19
|
+
checkWasmAvailability: () => checkWasmAvailability,
|
|
20
|
+
getVerificationMethod: () => getVerificationMethod,
|
|
21
|
+
getWasmConfidenceBoost: () => getWasmConfidenceBoost,
|
|
22
|
+
shouldIndicateWasmVerification: () => shouldIndicateWasmVerification
|
|
23
|
+
});
|
|
24
|
+
async function checkWasmAvailability() {
|
|
25
|
+
try {
|
|
26
|
+
if (typeof WebAssembly === "undefined") {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (!WebAssembly.instantiate || !WebAssembly.Module) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function shouldIndicateWasmVerification(confidence) {
|
|
38
|
+
return confidence >= 85 && confidence < 100;
|
|
39
|
+
}
|
|
40
|
+
function getWasmConfidenceBoost(baseConfidence, reasons = []) {
|
|
41
|
+
if (reasons.some(
|
|
42
|
+
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
43
|
+
)) {
|
|
44
|
+
return 100;
|
|
45
|
+
}
|
|
46
|
+
if (baseConfidence >= 85) {
|
|
47
|
+
return 95;
|
|
48
|
+
}
|
|
49
|
+
if (baseConfidence >= 70) {
|
|
50
|
+
return Math.min(baseConfidence * 1.1, 90);
|
|
51
|
+
}
|
|
52
|
+
return baseConfidence;
|
|
53
|
+
}
|
|
54
|
+
function getVerificationMethod(confidence, reasons = []) {
|
|
55
|
+
if (reasons.some(
|
|
56
|
+
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
57
|
+
)) {
|
|
58
|
+
return "signature";
|
|
59
|
+
}
|
|
60
|
+
if (shouldIndicateWasmVerification(confidence)) {
|
|
61
|
+
return "wasm-enhanced";
|
|
62
|
+
}
|
|
63
|
+
return "pattern";
|
|
64
|
+
}
|
|
65
|
+
var init_wasm_confidence = __esm({
|
|
66
|
+
"src/wasm-confidence.ts"() {
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// src/wasm-loader.ts
|
|
71
|
+
function setWasmBaseUrl(url) {
|
|
72
|
+
baseUrl = url;
|
|
73
|
+
}
|
|
74
|
+
function getWasmUrl() {
|
|
75
|
+
if (baseUrl) {
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(baseUrl);
|
|
78
|
+
return `${url.origin}${WASM_PATH}`;
|
|
79
|
+
} catch {
|
|
80
|
+
return WASM_PATH;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return WASM_PATH;
|
|
84
|
+
}
|
|
85
|
+
async function initWasm() {
|
|
86
|
+
if (wasmExports) return true;
|
|
87
|
+
if (initPromise) {
|
|
88
|
+
await initPromise;
|
|
89
|
+
return !!wasmExports;
|
|
90
|
+
}
|
|
91
|
+
initPromise = (async () => {
|
|
92
|
+
try {
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
95
|
+
try {
|
|
96
|
+
const wasmUrl = getWasmUrl();
|
|
97
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
98
|
+
try {
|
|
99
|
+
const response2 = await fetch(wasmUrl, { signal: controller.signal });
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
if (!response2.ok) {
|
|
102
|
+
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
103
|
+
}
|
|
104
|
+
const streamResponse = response2.clone();
|
|
105
|
+
const { instance } = await WebAssembly.instantiateStreaming(streamResponse, {
|
|
106
|
+
wbg: {
|
|
107
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
108
|
+
if (process.env.NODE_ENV !== "production") {
|
|
109
|
+
console.debug("WASM:", ptr, len);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
__wbindgen_throw: (ptr, len) => {
|
|
113
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
wasmInstance = instance;
|
|
118
|
+
wasmExports = instance.exports;
|
|
119
|
+
if (process.env.NODE_ENV !== "production") {
|
|
120
|
+
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
} catch (streamError) {
|
|
124
|
+
if (!controller.signal.aborted) {
|
|
125
|
+
if (process.env.NODE_ENV !== "production") {
|
|
126
|
+
console.debug(
|
|
127
|
+
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
throw streamError;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const response = await fetch(wasmUrl, { signal: controller.signal });
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`Failed to fetch WASM: ${response.status}`);
|
|
139
|
+
}
|
|
140
|
+
const wasmArrayBuffer = await response.arrayBuffer();
|
|
141
|
+
const compiledModule = await WebAssembly.compile(wasmArrayBuffer);
|
|
142
|
+
const imports = {
|
|
143
|
+
wbg: {
|
|
144
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
145
|
+
if (process.env.NODE_ENV !== "production") {
|
|
146
|
+
console.debug("WASM:", ptr, len);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
__wbindgen_throw: (ptr, len) => {
|
|
150
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
155
|
+
wasmExports = wasmInstance.exports;
|
|
156
|
+
if (process.env.NODE_ENV !== "production") {
|
|
157
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
158
|
+
}
|
|
159
|
+
} catch (fetchError) {
|
|
160
|
+
const error = fetchError;
|
|
161
|
+
if (error.name === "AbortError") {
|
|
162
|
+
console.warn(
|
|
163
|
+
"[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection"
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
console.warn(
|
|
167
|
+
"[AgentShield] Failed to fetch WASM file:",
|
|
168
|
+
error.message || "Unknown error"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
wasmExports = null;
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
175
|
+
wasmExports = null;
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
await initPromise;
|
|
179
|
+
return !!wasmExports;
|
|
180
|
+
}
|
|
181
|
+
async function detectAgentWithWasm(userAgent, headers, ipAddress) {
|
|
182
|
+
const initialized = await initWasm();
|
|
183
|
+
if (!initialized || !wasmExports) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const headersJson = JSON.stringify(headers);
|
|
188
|
+
if (typeof wasmExports.detect_agent === "function") {
|
|
189
|
+
const result = wasmExports.detect_agent(
|
|
190
|
+
userAgent || "",
|
|
191
|
+
headersJson,
|
|
192
|
+
ipAddress || "",
|
|
193
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
194
|
+
);
|
|
195
|
+
return {
|
|
196
|
+
isAgent: result.is_agent || false,
|
|
197
|
+
confidence: result.confidence || 0,
|
|
198
|
+
agent: result.agent,
|
|
199
|
+
verificationMethod: result.verification_method || "wasm",
|
|
200
|
+
riskLevel: result.risk_level || "low"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
console.warn("[AgentShield] WASM exports do not include detect_agent function");
|
|
204
|
+
return null;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("[AgentShield] WASM detection failed:", error);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function getWasmVersion() {
|
|
211
|
+
const initialized = await initWasm();
|
|
212
|
+
if (!initialized || !wasmExports) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (typeof wasmExports.version === "function") {
|
|
216
|
+
return wasmExports.version();
|
|
217
|
+
}
|
|
218
|
+
return "unknown";
|
|
219
|
+
}
|
|
220
|
+
async function isWasmAvailable() {
|
|
221
|
+
try {
|
|
222
|
+
const initialized = await initWasm();
|
|
223
|
+
if (!initialized) return false;
|
|
224
|
+
const version = await getWasmVersion();
|
|
225
|
+
return version !== null;
|
|
226
|
+
} catch {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
var wasmInstance, wasmExports, initPromise, WASM_PATH, baseUrl;
|
|
231
|
+
var init_wasm_loader = __esm({
|
|
232
|
+
"src/wasm-loader.ts"() {
|
|
233
|
+
wasmInstance = null;
|
|
234
|
+
wasmExports = null;
|
|
235
|
+
initPromise = null;
|
|
236
|
+
WASM_PATH = "/wasm/agentshield_wasm_bg.wasm";
|
|
237
|
+
baseUrl = null;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// src/edge-detector-with-wasm.ts
|
|
242
|
+
var edge_detector_with_wasm_exports = {};
|
|
243
|
+
__export(edge_detector_with_wasm_exports, {
|
|
244
|
+
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
245
|
+
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
246
|
+
});
|
|
247
|
+
var rules, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
248
|
+
var init_edge_detector_with_wasm = __esm({
|
|
249
|
+
"src/edge-detector-with-wasm.ts"() {
|
|
250
|
+
init_wasm_loader();
|
|
251
|
+
rules = agentshieldShared.loadRulesSync();
|
|
252
|
+
EdgeAgentDetectorWithWasm = class {
|
|
253
|
+
constructor(enableWasm = true) {
|
|
254
|
+
this.enableWasm = enableWasm;
|
|
255
|
+
this.rules = rules;
|
|
256
|
+
}
|
|
257
|
+
wasmEnabled = false;
|
|
258
|
+
initPromise = null;
|
|
259
|
+
baseUrl = null;
|
|
260
|
+
rules;
|
|
261
|
+
/**
|
|
262
|
+
* Set the base URL for WASM loading in Edge Runtime
|
|
263
|
+
*/
|
|
264
|
+
setBaseUrl(url) {
|
|
265
|
+
this.baseUrl = url;
|
|
266
|
+
setWasmBaseUrl(url);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Initialize the detector (including WASM if enabled)
|
|
270
|
+
*/
|
|
271
|
+
async init() {
|
|
272
|
+
if (!this.enableWasm) {
|
|
273
|
+
this.wasmEnabled = false;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (this.initPromise) {
|
|
277
|
+
await this.initPromise;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this.initPromise = (async () => {
|
|
281
|
+
try {
|
|
282
|
+
const wasmAvailable = await isWasmAvailable();
|
|
283
|
+
if (wasmAvailable) {
|
|
284
|
+
if (this.baseUrl) {
|
|
285
|
+
setWasmBaseUrl(this.baseUrl);
|
|
286
|
+
}
|
|
287
|
+
await initWasm();
|
|
288
|
+
this.wasmEnabled = true;
|
|
289
|
+
} else {
|
|
290
|
+
this.wasmEnabled = false;
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
294
|
+
this.wasmEnabled = false;
|
|
295
|
+
}
|
|
296
|
+
})();
|
|
297
|
+
await this.initPromise;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Pattern-based detection (fallback)
|
|
301
|
+
*/
|
|
302
|
+
async patternDetection(input) {
|
|
303
|
+
const reasons = [];
|
|
304
|
+
let detectedAgent;
|
|
305
|
+
let verificationMethod;
|
|
306
|
+
let confidence = 0;
|
|
307
|
+
const headers = input.headers || {};
|
|
308
|
+
const normalizedHeaders = {};
|
|
309
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
310
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
311
|
+
}
|
|
312
|
+
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
313
|
+
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
314
|
+
if (signatureAgent?.includes("chatgpt.com")) {
|
|
315
|
+
confidence = 85;
|
|
316
|
+
reasons.push("signature_agent:chatgpt");
|
|
317
|
+
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
318
|
+
verificationMethod = "signature";
|
|
319
|
+
} else if (signaturePresent) {
|
|
320
|
+
confidence = Math.max(confidence, 40);
|
|
321
|
+
reasons.push("signature_present");
|
|
322
|
+
}
|
|
323
|
+
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
324
|
+
if (userAgent) {
|
|
325
|
+
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
326
|
+
const matched = agentRule.patterns.some((pattern) => {
|
|
327
|
+
const regex = new RegExp(pattern, "i");
|
|
328
|
+
return regex.test(userAgent);
|
|
329
|
+
});
|
|
330
|
+
if (matched) {
|
|
331
|
+
const agentType = this.getAgentType(agentKey);
|
|
332
|
+
const agentName = this.getAgentName(agentKey);
|
|
333
|
+
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
334
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
335
|
+
if (!detectedAgent) {
|
|
336
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
337
|
+
verificationMethod = "pattern";
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
344
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
345
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
346
|
+
);
|
|
347
|
+
if (foundAiHeaders.length > 0) {
|
|
348
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
349
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
350
|
+
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
351
|
+
}
|
|
352
|
+
const ip = input.ip || input.ipAddress;
|
|
353
|
+
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
354
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
355
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
356
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
357
|
+
continue;
|
|
358
|
+
const matched = ipRule.ranges.some((range) => {
|
|
359
|
+
const prefix = range.split("/")[0];
|
|
360
|
+
const prefixParts = prefix.split(".");
|
|
361
|
+
const ipParts = ip.split(".");
|
|
362
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
363
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
});
|
|
369
|
+
if (matched) {
|
|
370
|
+
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
371
|
+
reasons.push(`cloud_provider:${provider}`);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (reasons.length > 2) {
|
|
377
|
+
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
378
|
+
}
|
|
379
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
380
|
+
return {
|
|
381
|
+
isAgent: confidence > 30,
|
|
382
|
+
// 30% threshold
|
|
383
|
+
confidence,
|
|
384
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
385
|
+
signals: [],
|
|
386
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
387
|
+
...detectedAgent && { detectedAgent },
|
|
388
|
+
reasons,
|
|
389
|
+
...verificationMethod && {
|
|
390
|
+
verificationMethod: agentshieldShared.mapVerificationMethod(verificationMethod)
|
|
391
|
+
},
|
|
392
|
+
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
393
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Analyze request with WASM enhancement when available
|
|
398
|
+
*/
|
|
399
|
+
async analyze(input) {
|
|
400
|
+
await this.init();
|
|
401
|
+
if (this.wasmEnabled) {
|
|
402
|
+
try {
|
|
403
|
+
const wasmResult = await detectAgentWithWasm(
|
|
404
|
+
input.userAgent || input.headers?.["user-agent"],
|
|
405
|
+
input.headers || {},
|
|
406
|
+
input.ip || input.ipAddress
|
|
407
|
+
);
|
|
408
|
+
if (wasmResult) {
|
|
409
|
+
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
410
|
+
return {
|
|
411
|
+
isAgent: wasmResult.isAgent,
|
|
412
|
+
confidence: wasmResult.confidence,
|
|
413
|
+
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
414
|
+
signals: [],
|
|
415
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
416
|
+
...detectedAgent && { detectedAgent },
|
|
417
|
+
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
418
|
+
verificationMethod: agentshieldShared.mapVerificationMethod(wasmResult.verificationMethod),
|
|
419
|
+
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
420
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error("[AgentShield] WASM detection error:", error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const patternResult = await this.patternDetection(input);
|
|
428
|
+
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
429
|
+
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
430
|
+
patternResult.reasons.push("wasm_enhanced");
|
|
431
|
+
}
|
|
432
|
+
return patternResult;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get agent type from rule key
|
|
436
|
+
*/
|
|
437
|
+
getAgentType(agentKey) {
|
|
438
|
+
const typeMap = {
|
|
439
|
+
openai_gptbot: "openai",
|
|
440
|
+
anthropic_claude: "anthropic",
|
|
441
|
+
perplexity_bot: "perplexity",
|
|
442
|
+
google_ai: "google",
|
|
443
|
+
microsoft_ai: "microsoft",
|
|
444
|
+
meta_ai: "meta",
|
|
445
|
+
cohere_bot: "cohere",
|
|
446
|
+
huggingface_bot: "huggingface",
|
|
447
|
+
generic_bot: "generic",
|
|
448
|
+
dev_tools: "dev",
|
|
449
|
+
automation_tools: "automation"
|
|
450
|
+
};
|
|
451
|
+
return typeMap[agentKey] || agentKey;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get agent name from rule key
|
|
455
|
+
*/
|
|
456
|
+
getAgentName(agentKey) {
|
|
457
|
+
const nameMap = {
|
|
458
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
459
|
+
anthropic_claude: "Claude",
|
|
460
|
+
perplexity_bot: "Perplexity",
|
|
461
|
+
google_ai: "Google AI",
|
|
462
|
+
microsoft_ai: "Microsoft Copilot",
|
|
463
|
+
meta_ai: "Meta AI",
|
|
464
|
+
cohere_bot: "Cohere",
|
|
465
|
+
huggingface_bot: "HuggingFace",
|
|
466
|
+
generic_bot: "Generic Bot",
|
|
467
|
+
dev_tools: "Development Tool",
|
|
468
|
+
automation_tools: "Automation Tool"
|
|
469
|
+
};
|
|
470
|
+
return nameMap[agentKey] || agentKey;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Map agent names from WASM to consistent format
|
|
474
|
+
*/
|
|
475
|
+
mapAgentName(agent) {
|
|
476
|
+
const lowerAgent = agent.toLowerCase();
|
|
477
|
+
if (lowerAgent.includes("chatgpt")) {
|
|
478
|
+
return { type: "chatgpt", name: "ChatGPT" };
|
|
479
|
+
} else if (lowerAgent.includes("claude")) {
|
|
480
|
+
return { type: "claude", name: "Claude" };
|
|
481
|
+
} else if (lowerAgent.includes("perplexity")) {
|
|
482
|
+
return { type: "perplexity", name: "Perplexity" };
|
|
483
|
+
} else if (lowerAgent.includes("bing")) {
|
|
484
|
+
return { type: "bing", name: "Bing AI" };
|
|
485
|
+
} else if (lowerAgent.includes("anthropic")) {
|
|
486
|
+
return { type: "anthropic", name: "Anthropic" };
|
|
487
|
+
}
|
|
488
|
+
return { type: "unknown", name: agent };
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
EdgeAgentDetectorWrapperWithWasm = class {
|
|
492
|
+
detector;
|
|
493
|
+
events = /* @__PURE__ */ new Map();
|
|
494
|
+
constructor(config) {
|
|
495
|
+
this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true);
|
|
496
|
+
if (config?.baseUrl) {
|
|
497
|
+
this.detector.setBaseUrl(config.baseUrl);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
setBaseUrl(url) {
|
|
501
|
+
this.detector.setBaseUrl(url);
|
|
502
|
+
}
|
|
503
|
+
async analyze(input) {
|
|
504
|
+
const result = await this.detector.analyze(input);
|
|
505
|
+
if (result.isAgent && this.events.has("agent.detected")) {
|
|
506
|
+
const handlers = this.events.get("agent.detected") || [];
|
|
507
|
+
handlers.forEach((handler) => handler(result, input));
|
|
508
|
+
}
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
on(event, handler) {
|
|
512
|
+
if (!this.events.has(event)) {
|
|
513
|
+
this.events.set(event, []);
|
|
514
|
+
}
|
|
515
|
+
this.events.get(event).push(handler);
|
|
516
|
+
}
|
|
517
|
+
emit(event, ...args) {
|
|
518
|
+
const handlers = this.events.get(event) || [];
|
|
519
|
+
handlers.forEach((handler) => handler(...args));
|
|
520
|
+
}
|
|
521
|
+
async init() {
|
|
522
|
+
await this.detector.init();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// src/edge-safe-detector.ts
|
|
529
|
+
var AI_AGENT_PATTERNS = [
|
|
530
|
+
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
531
|
+
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
532
|
+
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
533
|
+
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
534
|
+
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
535
|
+
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
536
|
+
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
537
|
+
];
|
|
538
|
+
var EdgeSafeDetector = class {
|
|
539
|
+
async analyze(input) {
|
|
540
|
+
const reasons = [];
|
|
541
|
+
let detectedAgent;
|
|
542
|
+
let confidence = 0;
|
|
543
|
+
const headers = input.headers || {};
|
|
544
|
+
const normalizedHeaders = {};
|
|
545
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
546
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
547
|
+
}
|
|
548
|
+
const userAgent = input.userAgent || normalizedHeaders["user-agent"] || "";
|
|
549
|
+
if (userAgent) {
|
|
550
|
+
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
551
|
+
if (pattern.test(userAgent)) {
|
|
552
|
+
confidence = 85;
|
|
553
|
+
reasons.push(`known_pattern:${type}`);
|
|
554
|
+
detectedAgent = { type, name };
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const hasChrome = userAgent.toLowerCase().includes("chrome");
|
|
560
|
+
const hasFirefox = userAgent.toLowerCase().includes("firefox");
|
|
561
|
+
const hasSafari = userAgent.toLowerCase().includes("safari");
|
|
562
|
+
const hasBrowserUA = hasChrome || hasFirefox || hasSafari;
|
|
563
|
+
if (hasBrowserUA) {
|
|
564
|
+
const hasSecChUa = !!normalizedHeaders["sec-ch-ua"];
|
|
565
|
+
const hasSecFetch = !!normalizedHeaders["sec-fetch-site"];
|
|
566
|
+
const hasAcceptLanguage = !!normalizedHeaders["accept-language"];
|
|
567
|
+
const hasCookies = !!normalizedHeaders["cookie"];
|
|
568
|
+
const missingHeaders = [];
|
|
569
|
+
if (!hasSecChUa && hasChrome) missingHeaders.push("sec-ch-ua");
|
|
570
|
+
if (!hasSecFetch) missingHeaders.push("sec-fetch");
|
|
571
|
+
if (!hasAcceptLanguage) missingHeaders.push("accept-language");
|
|
572
|
+
if (!hasCookies) missingHeaders.push("cookies");
|
|
573
|
+
if (missingHeaders.length >= 2) {
|
|
574
|
+
confidence = Math.max(confidence, 85);
|
|
575
|
+
reasons.push("browser_ua_missing_headers");
|
|
576
|
+
if (!detectedAgent && hasChrome && !hasSecChUa) {
|
|
577
|
+
detectedAgent = { type: "perplexity", name: "Perplexity" };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"];
|
|
582
|
+
const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]);
|
|
583
|
+
if (foundAiHeaders.length > 0) {
|
|
584
|
+
confidence = Math.max(confidence, 60);
|
|
585
|
+
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
isAgent: confidence > 30,
|
|
589
|
+
// Updated to 0-100 scale (was 0.3)
|
|
590
|
+
confidence,
|
|
591
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
592
|
+
signals: [],
|
|
593
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
594
|
+
detectedAgent,
|
|
595
|
+
reasons,
|
|
596
|
+
verificationMethod: "pattern",
|
|
597
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
598
|
+
confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low"
|
|
599
|
+
// Updated to 0-100 scale
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/storage/memory-adapter.ts
|
|
605
|
+
var MemoryStorageAdapter = class {
|
|
606
|
+
events = /* @__PURE__ */ new Map();
|
|
607
|
+
sessions = /* @__PURE__ */ new Map();
|
|
608
|
+
eventTimeline = [];
|
|
609
|
+
maxEvents = 1e3;
|
|
610
|
+
maxSessions = 100;
|
|
611
|
+
async storeEvent(event) {
|
|
612
|
+
const eventKey = `${event.timestamp}:${event.eventId}`;
|
|
613
|
+
this.events.set(eventKey, event);
|
|
614
|
+
this.eventTimeline.push({
|
|
615
|
+
timestamp: Date.parse(event.timestamp),
|
|
616
|
+
eventId: eventKey
|
|
617
|
+
});
|
|
618
|
+
this.eventTimeline.sort((a, b) => b.timestamp - a.timestamp);
|
|
619
|
+
if (this.eventTimeline.length > this.maxEvents) {
|
|
620
|
+
const removed = this.eventTimeline.splice(this.maxEvents);
|
|
621
|
+
removed.forEach((item) => this.events.delete(item.eventId));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async getRecentEvents(limit = 100) {
|
|
625
|
+
const recent = this.eventTimeline.slice(0, limit);
|
|
626
|
+
return recent.map((item) => this.events.get(item.eventId)).filter((event) => event !== void 0);
|
|
627
|
+
}
|
|
628
|
+
async getSessionEvents(sessionId) {
|
|
629
|
+
const events = [];
|
|
630
|
+
for (const event of this.events.values()) {
|
|
631
|
+
if (event.sessionId === sessionId) {
|
|
632
|
+
events.push(event);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return events.sort(
|
|
636
|
+
(a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
async storeSession(session) {
|
|
640
|
+
this.sessions.set(session.sessionId, session);
|
|
641
|
+
if (this.sessions.size > this.maxSessions) {
|
|
642
|
+
const sortedSessions = Array.from(this.sessions.entries()).sort((a, b) => Date.parse(b[1].lastSeen) - Date.parse(a[1].lastSeen));
|
|
643
|
+
const toRemove = sortedSessions.slice(this.maxSessions);
|
|
644
|
+
toRemove.forEach(([id]) => this.sessions.delete(id));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async getSession(sessionId) {
|
|
648
|
+
return this.sessions.get(sessionId) || null;
|
|
649
|
+
}
|
|
650
|
+
async getRecentSessions(limit = 10) {
|
|
651
|
+
const sorted = Array.from(this.sessions.values()).sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen));
|
|
652
|
+
return sorted.slice(0, limit);
|
|
653
|
+
}
|
|
654
|
+
async cleanup(olderThan) {
|
|
655
|
+
const cutoff = olderThan.getTime();
|
|
656
|
+
this.eventTimeline = this.eventTimeline.filter((item) => {
|
|
657
|
+
if (item.timestamp < cutoff) {
|
|
658
|
+
this.events.delete(item.eventId);
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
});
|
|
663
|
+
for (const [id, session] of this.sessions.entries()) {
|
|
664
|
+
if (Date.parse(session.lastSeen) < cutoff) {
|
|
665
|
+
this.sessions.delete(id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/storage/redis-adapter.ts
|
|
672
|
+
var RedisStorageAdapter = class {
|
|
673
|
+
redis;
|
|
674
|
+
ttl;
|
|
675
|
+
keyPrefix = "agent-shield";
|
|
676
|
+
constructor(redis, ttl = 86400) {
|
|
677
|
+
this.redis = redis;
|
|
678
|
+
this.ttl = ttl;
|
|
679
|
+
}
|
|
680
|
+
eventKey(timestamp, eventId) {
|
|
681
|
+
return `${this.keyPrefix}:events:${timestamp}:${eventId}`;
|
|
682
|
+
}
|
|
683
|
+
sessionKey(sessionId) {
|
|
684
|
+
return `${this.keyPrefix}:sessions:${sessionId}`;
|
|
685
|
+
}
|
|
686
|
+
timelineKey() {
|
|
687
|
+
return `${this.keyPrefix}:events:timeline`;
|
|
688
|
+
}
|
|
689
|
+
async storeEvent(event) {
|
|
690
|
+
const key = this.eventKey(event.timestamp, event.eventId);
|
|
691
|
+
await this.redis.setex(key, this.ttl, JSON.stringify(event));
|
|
692
|
+
await this.redis.zadd(this.timelineKey(), {
|
|
693
|
+
score: Date.parse(event.timestamp),
|
|
694
|
+
member: key
|
|
695
|
+
});
|
|
696
|
+
const count = await this.redis.zcard(this.timelineKey());
|
|
697
|
+
if (count && count > 1e3) {
|
|
698
|
+
await this.redis.zremrangebyrank(this.timelineKey(), 0, -1001);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async getRecentEvents(limit = 100) {
|
|
702
|
+
const keys = await this.redis.zrevrange(this.timelineKey(), 0, limit - 1);
|
|
703
|
+
if (!keys || keys.length === 0) {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
const events = [];
|
|
707
|
+
for (const key of keys) {
|
|
708
|
+
const data = await this.redis.get(key);
|
|
709
|
+
if (data) {
|
|
710
|
+
try {
|
|
711
|
+
const event = typeof data === "string" ? JSON.parse(data) : data;
|
|
712
|
+
events.push(event);
|
|
713
|
+
} catch (e) {
|
|
714
|
+
console.error(`Failed to parse event ${key}:`, e);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return events;
|
|
719
|
+
}
|
|
720
|
+
async getSessionEvents(sessionId) {
|
|
721
|
+
const keys = await this.redis.zrevrange(this.timelineKey(), 0, -1);
|
|
722
|
+
if (!keys || keys.length === 0) {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
const events = [];
|
|
726
|
+
for (const key of keys) {
|
|
727
|
+
const data = await this.redis.get(key);
|
|
728
|
+
if (data) {
|
|
729
|
+
try {
|
|
730
|
+
const event = typeof data === "string" ? JSON.parse(data) : data;
|
|
731
|
+
if (event.sessionId === sessionId) {
|
|
732
|
+
events.push(event);
|
|
733
|
+
}
|
|
734
|
+
} catch (e) {
|
|
735
|
+
console.error(`Failed to parse event ${key}:`, e);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return events;
|
|
740
|
+
}
|
|
741
|
+
async storeSession(session) {
|
|
742
|
+
const key = this.sessionKey(session.sessionId);
|
|
743
|
+
const existing = await this.redis.get(key);
|
|
744
|
+
if (existing) {
|
|
745
|
+
const existingSession = typeof existing === "string" ? JSON.parse(existing) : existing;
|
|
746
|
+
const methods = /* @__PURE__ */ new Set([
|
|
747
|
+
...existingSession.verificationMethods || [],
|
|
748
|
+
...session.verificationMethods
|
|
749
|
+
]);
|
|
750
|
+
const updatedSession = {
|
|
751
|
+
...existingSession,
|
|
752
|
+
lastSeen: session.lastSeen,
|
|
753
|
+
eventCount: session.eventCount,
|
|
754
|
+
paths: Array.from(/* @__PURE__ */ new Set([...existingSession.paths, ...session.paths])),
|
|
755
|
+
averageConfidence: session.averageConfidence,
|
|
756
|
+
verificationMethods: Array.from(methods)
|
|
757
|
+
};
|
|
758
|
+
await this.redis.setex(key, this.ttl, JSON.stringify(updatedSession));
|
|
759
|
+
} else {
|
|
760
|
+
await this.redis.setex(key, this.ttl, JSON.stringify(session));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async getSession(sessionId) {
|
|
764
|
+
const key = this.sessionKey(sessionId);
|
|
765
|
+
const data = await this.redis.get(key);
|
|
766
|
+
if (!data) {
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
return typeof data === "string" ? JSON.parse(data) : data;
|
|
771
|
+
} catch (e) {
|
|
772
|
+
console.error(`Failed to parse session ${sessionId}:`, e);
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async getRecentSessions(limit = 10) {
|
|
777
|
+
const pattern = `${this.keyPrefix}:sessions:*`;
|
|
778
|
+
const sessions = [];
|
|
779
|
+
let cursor = 0;
|
|
780
|
+
do {
|
|
781
|
+
const [nextCursor, keys] = await this.redis.scan(cursor, {
|
|
782
|
+
match: pattern,
|
|
783
|
+
count: 100
|
|
784
|
+
});
|
|
785
|
+
cursor = parseInt(nextCursor);
|
|
786
|
+
for (const key of keys) {
|
|
787
|
+
const data = await this.redis.get(key);
|
|
788
|
+
if (data) {
|
|
789
|
+
try {
|
|
790
|
+
const session = typeof data === "string" ? JSON.parse(data) : data;
|
|
791
|
+
sessions.push(session);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
console.error(`Failed to parse session from ${key}:`, e);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
} while (cursor !== 0 && sessions.length < limit * 2);
|
|
798
|
+
return sessions.sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)).slice(0, limit);
|
|
799
|
+
}
|
|
800
|
+
async cleanup(olderThan) {
|
|
801
|
+
const cutoff = olderThan.getTime();
|
|
802
|
+
await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3));
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/storage/index.ts
|
|
807
|
+
async function createStorageAdapter(config) {
|
|
808
|
+
if (!config || config.type === "memory") {
|
|
809
|
+
return new MemoryStorageAdapter();
|
|
810
|
+
}
|
|
811
|
+
if (config.type === "custom" && config.custom) {
|
|
812
|
+
return config.custom;
|
|
813
|
+
}
|
|
814
|
+
if (config.type === "redis" && config.redis) {
|
|
815
|
+
try {
|
|
816
|
+
const { Redis } = await import('@upstash/redis');
|
|
817
|
+
const redis = new Redis({
|
|
818
|
+
url: config.redis.url,
|
|
819
|
+
token: config.redis.token
|
|
820
|
+
});
|
|
821
|
+
return new RedisStorageAdapter(redis, config.ttl);
|
|
822
|
+
} catch (error) {
|
|
823
|
+
console.warn(
|
|
824
|
+
"[AgentShield] Failed to initialize Redis storage, falling back to memory:",
|
|
825
|
+
error
|
|
826
|
+
);
|
|
827
|
+
return new MemoryStorageAdapter();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return new MemoryStorageAdapter();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/enhanced-middleware.ts
|
|
834
|
+
var SessionManager = class {
|
|
835
|
+
sessionLastActivity = /* @__PURE__ */ new Map();
|
|
836
|
+
generateSessionId(ipAddress, userAgent) {
|
|
837
|
+
const now = Date.now();
|
|
838
|
+
const timeWindow = Math.floor(now / (5 * 60 * 1e3));
|
|
839
|
+
const baseKey = `${ipAddress || "unknown"}:${userAgent || "unknown"}`;
|
|
840
|
+
const windowKey = `${baseKey}:${timeWindow}`;
|
|
841
|
+
const lastActivity = this.sessionLastActivity.get(windowKey);
|
|
842
|
+
const shouldCreateNewSession = !lastActivity || now - lastActivity > 3e4;
|
|
843
|
+
this.sessionLastActivity.set(windowKey, now);
|
|
844
|
+
if (this.sessionLastActivity.size > 100) {
|
|
845
|
+
const cutoff = now - 6e5;
|
|
846
|
+
for (const [key, time] of this.sessionLastActivity.entries()) {
|
|
847
|
+
if (time < cutoff) {
|
|
848
|
+
this.sessionLastActivity.delete(key);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const data = shouldCreateNewSession ? `${windowKey}:${now}` : `${windowKey}:${lastActivity}`;
|
|
853
|
+
let hash = 0;
|
|
854
|
+
for (let i = 0; i < data.length; i++) {
|
|
855
|
+
const char = data.charCodeAt(i);
|
|
856
|
+
hash = (hash << 5) - hash + char;
|
|
857
|
+
hash = hash & hash;
|
|
858
|
+
}
|
|
859
|
+
return Math.abs(hash).toString(16).padStart(12, "0").substring(0, 12);
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
863
|
+
let storageAdapter = null;
|
|
864
|
+
let storageInitPromise = null;
|
|
865
|
+
const getStorage = async () => {
|
|
866
|
+
if (storageAdapter) return storageAdapter;
|
|
867
|
+
if (storageInitPromise) return storageInitPromise;
|
|
868
|
+
storageInitPromise = createStorageAdapter(config.storage).then((adapter) => {
|
|
869
|
+
storageAdapter = adapter;
|
|
870
|
+
return adapter;
|
|
871
|
+
});
|
|
872
|
+
return storageInitPromise;
|
|
873
|
+
};
|
|
874
|
+
let detector = null;
|
|
875
|
+
let detectorInitPromise = null;
|
|
876
|
+
let wasmConfidenceUtils = null;
|
|
877
|
+
const getDetector = async (requestUrl) => {
|
|
878
|
+
if (detector) {
|
|
879
|
+
return detector;
|
|
880
|
+
}
|
|
881
|
+
if (detectorInitPromise) {
|
|
882
|
+
await detectorInitPromise;
|
|
883
|
+
return detector;
|
|
884
|
+
}
|
|
885
|
+
detectorInitPromise = (async () => {
|
|
886
|
+
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge";
|
|
887
|
+
if (isEdgeRuntime) {
|
|
888
|
+
if (process.env.NODE_ENV !== "production") {
|
|
889
|
+
console.debug("[AgentShield] Edge Runtime detected - using pattern detection");
|
|
890
|
+
}
|
|
891
|
+
detector = new EdgeSafeDetector();
|
|
892
|
+
} else {
|
|
893
|
+
try {
|
|
894
|
+
try {
|
|
895
|
+
const wasmUtils = await Promise.resolve().then(() => (init_wasm_confidence(), wasm_confidence_exports));
|
|
896
|
+
wasmConfidenceUtils = wasmUtils;
|
|
897
|
+
const wasmAvailable = await wasmUtils.checkWasmAvailability();
|
|
898
|
+
if (wasmAvailable) {
|
|
899
|
+
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
900
|
+
if (process.env.NODE_ENV !== "production") {
|
|
901
|
+
console.debug(
|
|
902
|
+
"[AgentShield] \u2705 WASM support detected - enhanced detection enabled"
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
906
|
+
if (requestUrl && "setBaseUrl" in detector) {
|
|
907
|
+
detector.setBaseUrl(requestUrl);
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
if (process.env.NODE_ENV !== "production") {
|
|
911
|
+
console.debug(
|
|
912
|
+
"[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)"
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
detector = new EdgeSafeDetector();
|
|
916
|
+
}
|
|
917
|
+
} catch (wasmError) {
|
|
918
|
+
if (process.env.NODE_ENV !== "production") {
|
|
919
|
+
console.debug("[AgentShield] WASM utilities not available, using pattern detection");
|
|
920
|
+
}
|
|
921
|
+
detector = new EdgeSafeDetector();
|
|
922
|
+
}
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.warn("[AgentShield] Failed to initialize enhanced detector, using fallback");
|
|
925
|
+
detector = new EdgeSafeDetector();
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
})();
|
|
929
|
+
await detectorInitPromise;
|
|
930
|
+
return detector;
|
|
931
|
+
};
|
|
932
|
+
const sessionManager = new SessionManager();
|
|
933
|
+
const sessionTrackingEnabled = config.sessionTracking?.enabled !== false;
|
|
934
|
+
return async (request) => {
|
|
935
|
+
const { pathname } = request.nextUrl;
|
|
936
|
+
if (config.skipPaths?.some((path) => pathname.startsWith(path))) {
|
|
937
|
+
return server.NextResponse.next();
|
|
938
|
+
}
|
|
939
|
+
const userAgent = request.headers.get("user-agent");
|
|
940
|
+
const ipAddress = request.ip ?? request.headers.get("x-forwarded-for");
|
|
941
|
+
const url = new URL(request.url);
|
|
942
|
+
const pathWithQuery = url.pathname + url.search;
|
|
943
|
+
const context = {
|
|
944
|
+
userAgent: userAgent || "",
|
|
945
|
+
ipAddress: ipAddress || "",
|
|
946
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
947
|
+
url: pathWithQuery,
|
|
948
|
+
method: request.method,
|
|
949
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
950
|
+
};
|
|
951
|
+
const activeDetector = await getDetector(request.url);
|
|
952
|
+
const result = await activeDetector.analyze(context);
|
|
953
|
+
let finalConfidence = result.confidence;
|
|
954
|
+
let verificationMethod = result.verificationMethod || "pattern";
|
|
955
|
+
if (result.isAgent && wasmConfidenceUtils) {
|
|
956
|
+
const reasons = result.reasons || [];
|
|
957
|
+
if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) {
|
|
958
|
+
finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons);
|
|
959
|
+
verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
|
|
963
|
+
if (sessionTrackingEnabled) {
|
|
964
|
+
const storage = await getStorage();
|
|
965
|
+
const sessionId = sessionManager.generateSessionId(
|
|
966
|
+
ipAddress || void 0,
|
|
967
|
+
userAgent || void 0
|
|
968
|
+
);
|
|
969
|
+
const event = {
|
|
970
|
+
eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
971
|
+
sessionId,
|
|
972
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
973
|
+
agentType: result.detectedAgent?.type || "unknown",
|
|
974
|
+
agentName: result.detectedAgent?.name || "Unknown",
|
|
975
|
+
confidence: finalConfidence,
|
|
976
|
+
path: pathWithQuery,
|
|
977
|
+
...userAgent && { userAgent },
|
|
978
|
+
...ipAddress && { ipAddress },
|
|
979
|
+
method: request.method,
|
|
980
|
+
detectionReasons: result.reasons || [],
|
|
981
|
+
verificationMethod,
|
|
982
|
+
detectionDetails: {
|
|
983
|
+
patterns: result.detectedAgent?.patterns,
|
|
984
|
+
behaviors: result.detectedAgent?.behaviors,
|
|
985
|
+
fingerprintMatches: result.detectedAgent?.fingerprints
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
try {
|
|
989
|
+
await storage.storeEvent(event);
|
|
990
|
+
let session = await storage.getSession(sessionId);
|
|
991
|
+
if (session) {
|
|
992
|
+
session.lastSeen = event.timestamp;
|
|
993
|
+
session.eventCount++;
|
|
994
|
+
if (!session.paths.includes(pathWithQuery)) {
|
|
995
|
+
session.paths.push(pathWithQuery);
|
|
996
|
+
}
|
|
997
|
+
session.averageConfidence = (session.averageConfidence * (session.eventCount - 1) + finalConfidence) / session.eventCount;
|
|
998
|
+
if (!session.verificationMethods.includes(verificationMethod)) {
|
|
999
|
+
session.verificationMethods.push(verificationMethod);
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
session = {
|
|
1003
|
+
sessionId,
|
|
1004
|
+
...ipAddress && { ipAddress },
|
|
1005
|
+
...userAgent && { userAgent },
|
|
1006
|
+
agentType: result.detectedAgent?.type || "unknown",
|
|
1007
|
+
agentName: result.detectedAgent?.name || "Unknown",
|
|
1008
|
+
firstSeen: event.timestamp,
|
|
1009
|
+
lastSeen: event.timestamp,
|
|
1010
|
+
eventCount: 1,
|
|
1011
|
+
paths: [pathWithQuery],
|
|
1012
|
+
averageConfidence: finalConfidence,
|
|
1013
|
+
verificationMethods: [verificationMethod]
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (session) {
|
|
1017
|
+
await storage.storeSession(session);
|
|
1018
|
+
}
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
console.error("[AgentShield] Failed to store event:", error);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (config.onDetection) {
|
|
1024
|
+
await config.onDetection(result, context);
|
|
1025
|
+
}
|
|
1026
|
+
switch (config.onAgentDetected) {
|
|
1027
|
+
case "block": {
|
|
1028
|
+
const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {};
|
|
1029
|
+
const response2 = server.NextResponse.json(
|
|
1030
|
+
{ error: message, detected: true, confidence: finalConfidence },
|
|
1031
|
+
{ status }
|
|
1032
|
+
);
|
|
1033
|
+
response2.headers.set("x-agentshield-detected", "true");
|
|
1034
|
+
response2.headers.set(
|
|
1035
|
+
"x-agentshield-confidence",
|
|
1036
|
+
String(Math.round(finalConfidence * 100))
|
|
1037
|
+
);
|
|
1038
|
+
response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1039
|
+
response2.headers.set("x-agentshield-verification", verificationMethod);
|
|
1040
|
+
return response2;
|
|
1041
|
+
}
|
|
1042
|
+
case "log": {
|
|
1043
|
+
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
1044
|
+
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
1045
|
+
console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
|
|
1046
|
+
agent: result.detectedAgent?.name,
|
|
1047
|
+
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
1048
|
+
path: pathWithQuery,
|
|
1049
|
+
verification: verificationMethod
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const response = server.NextResponse.next();
|
|
1057
|
+
if (result.isAgent) {
|
|
1058
|
+
response.headers.set("x-agentshield-detected", "true");
|
|
1059
|
+
response.headers.set("x-agentshield-confidence", String(Math.round(finalConfidence * 100)));
|
|
1060
|
+
response.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1061
|
+
response.headers.set("x-agentshield-verification", verificationMethod);
|
|
1062
|
+
if (finalConfidence > 0.9) {
|
|
1063
|
+
response.headers.set("x-ai-visitor", "true");
|
|
1064
|
+
response.headers.set("x-ai-confidence", finalConfidence.toString());
|
|
1065
|
+
response.headers.set("x-ai-verification", verificationMethod);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return response;
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
exports.createEnhancedAgentShieldMiddleware = createEnhancedAgentShieldMiddleware;
|
|
1073
|
+
//# sourceMappingURL=enhanced-middleware.js.map
|
|
1074
|
+
//# sourceMappingURL=enhanced-middleware.js.map
|