@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/middleware.mjs
CHANGED
|
@@ -1,9 +1,457 @@
|
|
|
1
|
+
import { loadRulesSync, evaluateEnforcement, shouldEnforce, mapVerificationMethod } from '@kya-os/agentshield-shared';
|
|
1
2
|
import { NextResponse } from 'next/server';
|
|
2
3
|
import * as ed25519 from '@noble/ed25519';
|
|
3
4
|
import { sha512 } from '@noble/hashes/sha2.js';
|
|
4
|
-
import { loadRulesSync, mapVerificationMethod } from '@kya-os/agentshield-shared';
|
|
5
5
|
|
|
6
|
-
|
|
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-loader.ts
|
|
17
|
+
function setWasmBaseUrl(url) {
|
|
18
|
+
baseUrl = url;
|
|
19
|
+
}
|
|
20
|
+
function getWasmUrl() {
|
|
21
|
+
if (baseUrl) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(baseUrl);
|
|
24
|
+
return `${url.origin}${WASM_PATH}`;
|
|
25
|
+
} catch {
|
|
26
|
+
return WASM_PATH;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return WASM_PATH;
|
|
30
|
+
}
|
|
31
|
+
async function initWasm() {
|
|
32
|
+
if (wasmExports) return true;
|
|
33
|
+
if (initPromise) {
|
|
34
|
+
await initPromise;
|
|
35
|
+
return !!wasmExports;
|
|
36
|
+
}
|
|
37
|
+
initPromise = (async () => {
|
|
38
|
+
try {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
41
|
+
try {
|
|
42
|
+
const wasmUrl = getWasmUrl();
|
|
43
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
44
|
+
try {
|
|
45
|
+
const response2 = await fetch(wasmUrl, { signal: controller.signal });
|
|
46
|
+
clearTimeout(timeout);
|
|
47
|
+
if (!response2.ok) {
|
|
48
|
+
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
49
|
+
}
|
|
50
|
+
const streamResponse = response2.clone();
|
|
51
|
+
const { instance } = await WebAssembly.instantiateStreaming(streamResponse, {
|
|
52
|
+
wbg: {
|
|
53
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
54
|
+
if (process.env.NODE_ENV !== "production") {
|
|
55
|
+
console.debug("WASM:", ptr, len);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
__wbindgen_throw: (ptr, len) => {
|
|
59
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
wasmInstance = instance;
|
|
64
|
+
wasmExports = instance.exports;
|
|
65
|
+
if (process.env.NODE_ENV !== "production") {
|
|
66
|
+
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
} catch (streamError) {
|
|
70
|
+
if (!controller.signal.aborted) {
|
|
71
|
+
if (process.env.NODE_ENV !== "production") {
|
|
72
|
+
console.debug(
|
|
73
|
+
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
throw streamError;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const response = await fetch(wasmUrl, { signal: controller.signal });
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Failed to fetch WASM: ${response.status}`);
|
|
85
|
+
}
|
|
86
|
+
const wasmArrayBuffer = await response.arrayBuffer();
|
|
87
|
+
const compiledModule = await WebAssembly.compile(wasmArrayBuffer);
|
|
88
|
+
const imports = {
|
|
89
|
+
wbg: {
|
|
90
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
91
|
+
if (process.env.NODE_ENV !== "production") {
|
|
92
|
+
console.debug("WASM:", ptr, len);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
__wbindgen_throw: (ptr, len) => {
|
|
96
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
101
|
+
wasmExports = wasmInstance.exports;
|
|
102
|
+
if (process.env.NODE_ENV !== "production") {
|
|
103
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
104
|
+
}
|
|
105
|
+
} catch (fetchError) {
|
|
106
|
+
const error = fetchError;
|
|
107
|
+
if (error.name === "AbortError") {
|
|
108
|
+
console.warn(
|
|
109
|
+
"[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection"
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
console.warn(
|
|
113
|
+
"[AgentShield] Failed to fetch WASM file:",
|
|
114
|
+
error.message || "Unknown error"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
wasmExports = null;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
121
|
+
wasmExports = null;
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
await initPromise;
|
|
125
|
+
return !!wasmExports;
|
|
126
|
+
}
|
|
127
|
+
async function detectAgentWithWasm(_userAgent, _headers, _ipAddress) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
async function getWasmVersion() {
|
|
131
|
+
const initialized = await initWasm();
|
|
132
|
+
if (!initialized || !wasmExports) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (typeof wasmExports.version === "function") {
|
|
136
|
+
return wasmExports.version();
|
|
137
|
+
}
|
|
138
|
+
return "unknown";
|
|
139
|
+
}
|
|
140
|
+
async function isWasmAvailable() {
|
|
141
|
+
try {
|
|
142
|
+
const initialized = await initWasm();
|
|
143
|
+
if (!initialized) return false;
|
|
144
|
+
const version = await getWasmVersion();
|
|
145
|
+
return version !== null;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
var wasmInstance, wasmExports, initPromise, WASM_PATH, baseUrl;
|
|
151
|
+
var init_wasm_loader = __esm({
|
|
152
|
+
"src/wasm-loader.ts"() {
|
|
153
|
+
wasmInstance = null;
|
|
154
|
+
wasmExports = null;
|
|
155
|
+
initPromise = null;
|
|
156
|
+
WASM_PATH = "/wasm/agentshield_wasm_bg.wasm";
|
|
157
|
+
baseUrl = null;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// src/edge-detector-with-wasm.ts
|
|
162
|
+
var edge_detector_with_wasm_exports = {};
|
|
163
|
+
__export(edge_detector_with_wasm_exports, {
|
|
164
|
+
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
165
|
+
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
166
|
+
});
|
|
167
|
+
var rules2, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
168
|
+
var init_edge_detector_with_wasm = __esm({
|
|
169
|
+
"src/edge-detector-with-wasm.ts"() {
|
|
170
|
+
init_wasm_loader();
|
|
171
|
+
rules2 = loadRulesSync();
|
|
172
|
+
EdgeAgentDetectorWithWasm = class {
|
|
173
|
+
constructor(enableWasm = true) {
|
|
174
|
+
this.enableWasm = enableWasm;
|
|
175
|
+
this.rules = rules2;
|
|
176
|
+
}
|
|
177
|
+
wasmEnabled = false;
|
|
178
|
+
initPromise = null;
|
|
179
|
+
baseUrl = null;
|
|
180
|
+
rules;
|
|
181
|
+
/**
|
|
182
|
+
* Set the base URL for WASM loading in Edge Runtime
|
|
183
|
+
*/
|
|
184
|
+
setBaseUrl(url) {
|
|
185
|
+
this.baseUrl = url;
|
|
186
|
+
setWasmBaseUrl(url);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Initialize the detector (including WASM if enabled)
|
|
190
|
+
*/
|
|
191
|
+
async init() {
|
|
192
|
+
if (!this.enableWasm) {
|
|
193
|
+
this.wasmEnabled = false;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (this.initPromise) {
|
|
197
|
+
await this.initPromise;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.initPromise = (async () => {
|
|
201
|
+
try {
|
|
202
|
+
const wasmAvailable = await isWasmAvailable();
|
|
203
|
+
if (wasmAvailable) {
|
|
204
|
+
if (this.baseUrl) {
|
|
205
|
+
setWasmBaseUrl(this.baseUrl);
|
|
206
|
+
}
|
|
207
|
+
await initWasm();
|
|
208
|
+
this.wasmEnabled = true;
|
|
209
|
+
} else {
|
|
210
|
+
this.wasmEnabled = false;
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
214
|
+
this.wasmEnabled = false;
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
await this.initPromise;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Pattern-based detection (fallback)
|
|
221
|
+
*/
|
|
222
|
+
async patternDetection(input) {
|
|
223
|
+
const reasons = [];
|
|
224
|
+
let detectedAgent;
|
|
225
|
+
let verificationMethod;
|
|
226
|
+
let confidence = 0;
|
|
227
|
+
const headers = input.headers || {};
|
|
228
|
+
const normalizedHeaders = {};
|
|
229
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
230
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
231
|
+
}
|
|
232
|
+
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
233
|
+
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
234
|
+
const isChatGPT = (() => {
|
|
235
|
+
try {
|
|
236
|
+
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
237
|
+
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
})();
|
|
242
|
+
if (isChatGPT) {
|
|
243
|
+
confidence = 85;
|
|
244
|
+
reasons.push("signature_agent:chatgpt");
|
|
245
|
+
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
246
|
+
verificationMethod = "signature";
|
|
247
|
+
} else if (signaturePresent) {
|
|
248
|
+
confidence = Math.max(confidence, 40);
|
|
249
|
+
reasons.push("signature_present");
|
|
250
|
+
}
|
|
251
|
+
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
252
|
+
if (userAgent) {
|
|
253
|
+
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
254
|
+
const matched = agentRule.patterns.some((pattern) => {
|
|
255
|
+
const regex = new RegExp(pattern, "i");
|
|
256
|
+
return regex.test(userAgent);
|
|
257
|
+
});
|
|
258
|
+
if (matched) {
|
|
259
|
+
const agentType = this.getAgentType(agentKey);
|
|
260
|
+
const agentName = this.getAgentName(agentKey);
|
|
261
|
+
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
262
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
263
|
+
if (!detectedAgent) {
|
|
264
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
265
|
+
verificationMethod = "pattern";
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
272
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
273
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
274
|
+
);
|
|
275
|
+
if (foundAiHeaders.length > 0) {
|
|
276
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
277
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
278
|
+
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
279
|
+
}
|
|
280
|
+
const ip = input.ip || input.ipAddress;
|
|
281
|
+
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
282
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
283
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
284
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
285
|
+
continue;
|
|
286
|
+
const matched = ipRule.ranges.some((range) => {
|
|
287
|
+
const prefix = range.split("/")[0];
|
|
288
|
+
const prefixParts = prefix.split(".");
|
|
289
|
+
const ipParts = ip.split(".");
|
|
290
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
291
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return true;
|
|
296
|
+
});
|
|
297
|
+
if (matched) {
|
|
298
|
+
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
299
|
+
reasons.push(`cloud_provider:${provider}`);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (reasons.length > 2) {
|
|
305
|
+
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
306
|
+
}
|
|
307
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
308
|
+
return {
|
|
309
|
+
isAgent: confidence > 30,
|
|
310
|
+
// 30% threshold
|
|
311
|
+
confidence,
|
|
312
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
313
|
+
signals: [],
|
|
314
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
315
|
+
...detectedAgent && { detectedAgent },
|
|
316
|
+
reasons,
|
|
317
|
+
...verificationMethod && {
|
|
318
|
+
verificationMethod: mapVerificationMethod(verificationMethod)
|
|
319
|
+
},
|
|
320
|
+
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
321
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Analyze request with WASM enhancement when available
|
|
326
|
+
*/
|
|
327
|
+
async analyze(input) {
|
|
328
|
+
await this.init();
|
|
329
|
+
if (this.wasmEnabled) {
|
|
330
|
+
try {
|
|
331
|
+
const wasmResult = await detectAgentWithWasm(
|
|
332
|
+
input.userAgent || input.headers?.["user-agent"],
|
|
333
|
+
input.headers || {},
|
|
334
|
+
input.ip || input.ipAddress
|
|
335
|
+
);
|
|
336
|
+
if (wasmResult) {
|
|
337
|
+
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
338
|
+
return {
|
|
339
|
+
isAgent: wasmResult.isAgent,
|
|
340
|
+
confidence: wasmResult.confidence,
|
|
341
|
+
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
342
|
+
signals: [],
|
|
343
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
344
|
+
...detectedAgent && { detectedAgent },
|
|
345
|
+
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
346
|
+
verificationMethod: mapVerificationMethod(wasmResult.verificationMethod),
|
|
347
|
+
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
348
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error("[AgentShield] WASM detection error:", error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const patternResult = await this.patternDetection(input);
|
|
356
|
+
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
357
|
+
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
358
|
+
patternResult.reasons.push("wasm_enhanced");
|
|
359
|
+
}
|
|
360
|
+
return patternResult;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get agent type from rule key
|
|
364
|
+
*/
|
|
365
|
+
getAgentType(agentKey) {
|
|
366
|
+
const typeMap = {
|
|
367
|
+
openai_gptbot: "openai",
|
|
368
|
+
anthropic_claude: "anthropic",
|
|
369
|
+
perplexity_bot: "perplexity",
|
|
370
|
+
google_ai: "google",
|
|
371
|
+
microsoft_ai: "microsoft",
|
|
372
|
+
meta_ai: "meta",
|
|
373
|
+
cohere_bot: "cohere",
|
|
374
|
+
huggingface_bot: "huggingface",
|
|
375
|
+
generic_bot: "generic",
|
|
376
|
+
dev_tools: "dev",
|
|
377
|
+
automation_tools: "automation"
|
|
378
|
+
};
|
|
379
|
+
return typeMap[agentKey] || agentKey;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get agent name from rule key
|
|
383
|
+
*/
|
|
384
|
+
getAgentName(agentKey) {
|
|
385
|
+
const nameMap = {
|
|
386
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
387
|
+
anthropic_claude: "Claude",
|
|
388
|
+
perplexity_bot: "Perplexity",
|
|
389
|
+
google_ai: "Google AI",
|
|
390
|
+
microsoft_ai: "Microsoft Copilot",
|
|
391
|
+
meta_ai: "Meta AI",
|
|
392
|
+
cohere_bot: "Cohere",
|
|
393
|
+
huggingface_bot: "HuggingFace",
|
|
394
|
+
generic_bot: "Generic Bot",
|
|
395
|
+
dev_tools: "Development Tool",
|
|
396
|
+
automation_tools: "Automation Tool"
|
|
397
|
+
};
|
|
398
|
+
return nameMap[agentKey] || agentKey;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Map agent names from WASM to consistent format
|
|
402
|
+
*/
|
|
403
|
+
mapAgentName(agent) {
|
|
404
|
+
const lowerAgent = agent.toLowerCase();
|
|
405
|
+
if (lowerAgent.includes("chatgpt")) {
|
|
406
|
+
return { type: "chatgpt", name: "ChatGPT" };
|
|
407
|
+
} else if (lowerAgent.includes("claude")) {
|
|
408
|
+
return { type: "claude", name: "Claude" };
|
|
409
|
+
} else if (lowerAgent.includes("perplexity")) {
|
|
410
|
+
return { type: "perplexity", name: "Perplexity" };
|
|
411
|
+
} else if (lowerAgent.includes("bing")) {
|
|
412
|
+
return { type: "bing", name: "Bing AI" };
|
|
413
|
+
} else if (lowerAgent.includes("anthropic")) {
|
|
414
|
+
return { type: "anthropic", name: "Anthropic" };
|
|
415
|
+
}
|
|
416
|
+
return { type: "unknown", name: agent };
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
EdgeAgentDetectorWrapperWithWasm = class {
|
|
420
|
+
detector;
|
|
421
|
+
events = /* @__PURE__ */ new Map();
|
|
422
|
+
constructor(config) {
|
|
423
|
+
this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true);
|
|
424
|
+
if (config?.baseUrl) {
|
|
425
|
+
this.detector.setBaseUrl(config.baseUrl);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
setBaseUrl(url) {
|
|
429
|
+
this.detector.setBaseUrl(url);
|
|
430
|
+
}
|
|
431
|
+
async analyze(input) {
|
|
432
|
+
const result = await this.detector.analyze(input);
|
|
433
|
+
if (result.isAgent && this.events.has("agent.detected")) {
|
|
434
|
+
const handlers = this.events.get("agent.detected") || [];
|
|
435
|
+
handlers.forEach((handler) => handler(result, input));
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
on(event, handler) {
|
|
440
|
+
if (!this.events.has(event)) {
|
|
441
|
+
this.events.set(event, []);
|
|
442
|
+
}
|
|
443
|
+
this.events.get(event).push(handler);
|
|
444
|
+
}
|
|
445
|
+
emit(event, ...args) {
|
|
446
|
+
const handlers = this.events.get(event) || [];
|
|
447
|
+
handlers.forEach((handler) => handler(...args));
|
|
448
|
+
}
|
|
449
|
+
async init() {
|
|
450
|
+
await this.detector.init();
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
});
|
|
7
455
|
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
8
456
|
var KNOWN_KEYS = {
|
|
9
457
|
chatgpt: [
|
|
@@ -264,7 +712,15 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
264
712
|
}
|
|
265
713
|
let agent;
|
|
266
714
|
let agentKey;
|
|
267
|
-
|
|
715
|
+
const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
|
|
716
|
+
try {
|
|
717
|
+
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
718
|
+
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
719
|
+
} catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
})();
|
|
723
|
+
if (isChatGPT) {
|
|
268
724
|
agent = "ChatGPT";
|
|
269
725
|
agentKey = "chatgpt";
|
|
270
726
|
}
|
|
@@ -561,462 +1017,46 @@ var EdgeAgentDetectorWrapper = class {
|
|
|
561
1017
|
return;
|
|
562
1018
|
}
|
|
563
1019
|
};
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
1020
|
+
var EdgeSessionTracker = class {
|
|
1021
|
+
config;
|
|
1022
|
+
constructor(config) {
|
|
1023
|
+
this.config = {
|
|
1024
|
+
enabled: config.enabled,
|
|
1025
|
+
cookieName: config.cookieName || "__agentshield_session",
|
|
1026
|
+
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
1027
|
+
// 1 hour default
|
|
1028
|
+
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Track a new AI agent session
|
|
1033
|
+
*/
|
|
1034
|
+
async track(_request, response, result) {
|
|
576
1035
|
try {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (!response2.ok) {
|
|
602
|
-
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
603
|
-
}
|
|
604
|
-
const streamResponse = response2.clone();
|
|
605
|
-
const { instance } = await WebAssembly.instantiateStreaming(streamResponse, {
|
|
606
|
-
wbg: {
|
|
607
|
-
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
608
|
-
if (process.env.NODE_ENV !== "production") {
|
|
609
|
-
console.debug("WASM:", ptr, len);
|
|
610
|
-
}
|
|
611
|
-
},
|
|
612
|
-
__wbindgen_throw: (ptr, len) => {
|
|
613
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
wasmInstance = instance;
|
|
618
|
-
wasmExports = instance.exports;
|
|
619
|
-
if (process.env.NODE_ENV !== "production") {
|
|
620
|
-
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
621
|
-
}
|
|
622
|
-
return;
|
|
623
|
-
} catch (streamError) {
|
|
624
|
-
if (!controller.signal.aborted) {
|
|
625
|
-
if (process.env.NODE_ENV !== "production") {
|
|
626
|
-
console.debug(
|
|
627
|
-
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
} else {
|
|
631
|
-
throw streamError;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const response = await fetch(wasmUrl, { signal: controller.signal });
|
|
636
|
-
clearTimeout(timeout);
|
|
637
|
-
if (!response.ok) {
|
|
638
|
-
throw new Error(`Failed to fetch WASM: ${response.status}`);
|
|
639
|
-
}
|
|
640
|
-
const wasmArrayBuffer = await response.arrayBuffer();
|
|
641
|
-
const compiledModule = await WebAssembly.compile(wasmArrayBuffer);
|
|
642
|
-
const imports = {
|
|
643
|
-
wbg: {
|
|
644
|
-
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
645
|
-
if (process.env.NODE_ENV !== "production") {
|
|
646
|
-
console.debug("WASM:", ptr, len);
|
|
647
|
-
}
|
|
648
|
-
},
|
|
649
|
-
__wbindgen_throw: (ptr, len) => {
|
|
650
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
655
|
-
wasmExports = wasmInstance.exports;
|
|
656
|
-
if (process.env.NODE_ENV !== "production") {
|
|
657
|
-
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
658
|
-
}
|
|
659
|
-
} catch (fetchError) {
|
|
660
|
-
const error = fetchError;
|
|
661
|
-
if (error.name === "AbortError") {
|
|
662
|
-
console.warn(
|
|
663
|
-
"[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection"
|
|
664
|
-
);
|
|
665
|
-
} else {
|
|
666
|
-
console.warn(
|
|
667
|
-
"[AgentShield] Failed to fetch WASM file:",
|
|
668
|
-
error.message || "Unknown error"
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
wasmExports = null;
|
|
672
|
-
}
|
|
673
|
-
} catch (error) {
|
|
674
|
-
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
675
|
-
wasmExports = null;
|
|
676
|
-
}
|
|
677
|
-
})();
|
|
678
|
-
await initPromise;
|
|
679
|
-
return !!wasmExports;
|
|
680
|
-
}
|
|
681
|
-
async function detectAgentWithWasm(_userAgent, _headers, _ipAddress) {
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
async function getWasmVersion() {
|
|
685
|
-
const initialized = await initWasm();
|
|
686
|
-
if (!initialized || !wasmExports) {
|
|
687
|
-
return null;
|
|
688
|
-
}
|
|
689
|
-
if (typeof wasmExports.version === "function") {
|
|
690
|
-
return wasmExports.version();
|
|
691
|
-
}
|
|
692
|
-
return "unknown";
|
|
693
|
-
}
|
|
694
|
-
async function isWasmAvailable() {
|
|
695
|
-
try {
|
|
696
|
-
const initialized = await initWasm();
|
|
697
|
-
if (!initialized) return false;
|
|
698
|
-
const version = await getWasmVersion();
|
|
699
|
-
return version !== null;
|
|
700
|
-
} catch {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
var rules2 = loadRulesSync();
|
|
705
|
-
var EdgeAgentDetectorWithWasm = class {
|
|
706
|
-
constructor(enableWasm = true) {
|
|
707
|
-
this.enableWasm = enableWasm;
|
|
708
|
-
this.rules = rules2;
|
|
709
|
-
}
|
|
710
|
-
wasmEnabled = false;
|
|
711
|
-
initPromise = null;
|
|
712
|
-
baseUrl = null;
|
|
713
|
-
rules;
|
|
714
|
-
/**
|
|
715
|
-
* Set the base URL for WASM loading in Edge Runtime
|
|
716
|
-
*/
|
|
717
|
-
setBaseUrl(url) {
|
|
718
|
-
this.baseUrl = url;
|
|
719
|
-
setWasmBaseUrl(url);
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Initialize the detector (including WASM if enabled)
|
|
723
|
-
*/
|
|
724
|
-
async init() {
|
|
725
|
-
if (!this.enableWasm) {
|
|
726
|
-
this.wasmEnabled = false;
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
if (this.initPromise) {
|
|
730
|
-
await this.initPromise;
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
this.initPromise = (async () => {
|
|
734
|
-
try {
|
|
735
|
-
const wasmAvailable = await isWasmAvailable();
|
|
736
|
-
if (wasmAvailable) {
|
|
737
|
-
if (this.baseUrl) {
|
|
738
|
-
setWasmBaseUrl(this.baseUrl);
|
|
739
|
-
}
|
|
740
|
-
await initWasm();
|
|
741
|
-
this.wasmEnabled = true;
|
|
742
|
-
} else {
|
|
743
|
-
this.wasmEnabled = false;
|
|
744
|
-
}
|
|
745
|
-
} catch (error) {
|
|
746
|
-
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
747
|
-
this.wasmEnabled = false;
|
|
748
|
-
}
|
|
749
|
-
})();
|
|
750
|
-
await this.initPromise;
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Pattern-based detection (fallback)
|
|
754
|
-
*/
|
|
755
|
-
async patternDetection(input) {
|
|
756
|
-
const reasons = [];
|
|
757
|
-
let detectedAgent;
|
|
758
|
-
let verificationMethod;
|
|
759
|
-
let confidence = 0;
|
|
760
|
-
const headers = input.headers || {};
|
|
761
|
-
const normalizedHeaders = {};
|
|
762
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
763
|
-
normalizedHeaders[key.toLowerCase()] = value;
|
|
764
|
-
}
|
|
765
|
-
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
766
|
-
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
767
|
-
if (signatureAgent?.includes("chatgpt.com")) {
|
|
768
|
-
confidence = 85;
|
|
769
|
-
reasons.push("signature_agent:chatgpt");
|
|
770
|
-
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
771
|
-
verificationMethod = "signature";
|
|
772
|
-
} else if (signaturePresent) {
|
|
773
|
-
confidence = Math.max(confidence, 40);
|
|
774
|
-
reasons.push("signature_present");
|
|
775
|
-
}
|
|
776
|
-
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
777
|
-
if (userAgent) {
|
|
778
|
-
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
779
|
-
const matched = agentRule.patterns.some((pattern) => {
|
|
780
|
-
const regex = new RegExp(pattern, "i");
|
|
781
|
-
return regex.test(userAgent);
|
|
782
|
-
});
|
|
783
|
-
if (matched) {
|
|
784
|
-
const agentType = this.getAgentType(agentKey);
|
|
785
|
-
const agentName = this.getAgentName(agentKey);
|
|
786
|
-
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
787
|
-
reasons.push(`known_pattern:${agentType}`);
|
|
788
|
-
if (!detectedAgent) {
|
|
789
|
-
detectedAgent = { type: agentType, name: agentName };
|
|
790
|
-
verificationMethod = "pattern";
|
|
791
|
-
}
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
797
|
-
const foundAiHeaders = suspiciousHeaders.filter(
|
|
798
|
-
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
799
|
-
);
|
|
800
|
-
if (foundAiHeaders.length > 0) {
|
|
801
|
-
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
802
|
-
confidence = Math.max(confidence, maxConfidence);
|
|
803
|
-
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
804
|
-
}
|
|
805
|
-
const ip = input.ip || input.ipAddress;
|
|
806
|
-
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
807
|
-
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
808
|
-
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
809
|
-
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
810
|
-
continue;
|
|
811
|
-
const matched = ipRule.ranges.some((range) => {
|
|
812
|
-
const prefix = range.split("/")[0];
|
|
813
|
-
const prefixParts = prefix.split(".");
|
|
814
|
-
const ipParts = ip.split(".");
|
|
815
|
-
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
816
|
-
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
817
|
-
return false;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return true;
|
|
821
|
-
});
|
|
822
|
-
if (matched) {
|
|
823
|
-
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
824
|
-
reasons.push(`cloud_provider:${provider}`);
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
if (reasons.length > 2) {
|
|
830
|
-
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
831
|
-
}
|
|
832
|
-
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
833
|
-
return {
|
|
834
|
-
isAgent: confidence > 30,
|
|
835
|
-
// 30% threshold
|
|
836
|
-
confidence,
|
|
837
|
-
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
838
|
-
signals: [],
|
|
839
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
840
|
-
...detectedAgent && { detectedAgent },
|
|
841
|
-
reasons,
|
|
842
|
-
...verificationMethod && {
|
|
843
|
-
verificationMethod: mapVerificationMethod(verificationMethod)
|
|
844
|
-
},
|
|
845
|
-
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
846
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Analyze request with WASM enhancement when available
|
|
851
|
-
*/
|
|
852
|
-
async analyze(input) {
|
|
853
|
-
await this.init();
|
|
854
|
-
if (this.wasmEnabled) {
|
|
855
|
-
try {
|
|
856
|
-
const wasmResult = await detectAgentWithWasm(
|
|
857
|
-
input.userAgent || input.headers?.["user-agent"],
|
|
858
|
-
input.headers || {},
|
|
859
|
-
input.ip || input.ipAddress
|
|
860
|
-
);
|
|
861
|
-
if (wasmResult) {
|
|
862
|
-
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
863
|
-
return {
|
|
864
|
-
isAgent: wasmResult.isAgent,
|
|
865
|
-
confidence: wasmResult.confidence,
|
|
866
|
-
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
867
|
-
signals: [],
|
|
868
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
869
|
-
...detectedAgent && { detectedAgent },
|
|
870
|
-
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
871
|
-
verificationMethod: mapVerificationMethod(wasmResult.verificationMethod),
|
|
872
|
-
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
873
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
} catch (error) {
|
|
877
|
-
console.error("[AgentShield] WASM detection error:", error);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
const patternResult = await this.patternDetection(input);
|
|
881
|
-
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
882
|
-
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
883
|
-
patternResult.reasons.push("wasm_enhanced");
|
|
884
|
-
}
|
|
885
|
-
return patternResult;
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Get agent type from rule key
|
|
889
|
-
*/
|
|
890
|
-
getAgentType(agentKey) {
|
|
891
|
-
const typeMap = {
|
|
892
|
-
openai_gptbot: "openai",
|
|
893
|
-
anthropic_claude: "anthropic",
|
|
894
|
-
perplexity_bot: "perplexity",
|
|
895
|
-
google_ai: "google",
|
|
896
|
-
microsoft_ai: "microsoft",
|
|
897
|
-
meta_ai: "meta",
|
|
898
|
-
cohere_bot: "cohere",
|
|
899
|
-
huggingface_bot: "huggingface",
|
|
900
|
-
generic_bot: "generic",
|
|
901
|
-
dev_tools: "dev",
|
|
902
|
-
automation_tools: "automation"
|
|
903
|
-
};
|
|
904
|
-
return typeMap[agentKey] || agentKey;
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Get agent name from rule key
|
|
908
|
-
*/
|
|
909
|
-
getAgentName(agentKey) {
|
|
910
|
-
const nameMap = {
|
|
911
|
-
openai_gptbot: "ChatGPT/GPTBot",
|
|
912
|
-
anthropic_claude: "Claude",
|
|
913
|
-
perplexity_bot: "Perplexity",
|
|
914
|
-
google_ai: "Google AI",
|
|
915
|
-
microsoft_ai: "Microsoft Copilot",
|
|
916
|
-
meta_ai: "Meta AI",
|
|
917
|
-
cohere_bot: "Cohere",
|
|
918
|
-
huggingface_bot: "HuggingFace",
|
|
919
|
-
generic_bot: "Generic Bot",
|
|
920
|
-
dev_tools: "Development Tool",
|
|
921
|
-
automation_tools: "Automation Tool"
|
|
922
|
-
};
|
|
923
|
-
return nameMap[agentKey] || agentKey;
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Map agent names from WASM to consistent format
|
|
927
|
-
*/
|
|
928
|
-
mapAgentName(agent) {
|
|
929
|
-
const lowerAgent = agent.toLowerCase();
|
|
930
|
-
if (lowerAgent.includes("chatgpt")) {
|
|
931
|
-
return { type: "chatgpt", name: "ChatGPT" };
|
|
932
|
-
} else if (lowerAgent.includes("claude")) {
|
|
933
|
-
return { type: "claude", name: "Claude" };
|
|
934
|
-
} else if (lowerAgent.includes("perplexity")) {
|
|
935
|
-
return { type: "perplexity", name: "Perplexity" };
|
|
936
|
-
} else if (lowerAgent.includes("bing")) {
|
|
937
|
-
return { type: "bing", name: "Bing AI" };
|
|
938
|
-
} else if (lowerAgent.includes("anthropic")) {
|
|
939
|
-
return { type: "anthropic", name: "Anthropic" };
|
|
940
|
-
}
|
|
941
|
-
return { type: "unknown", name: agent };
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
var EdgeAgentDetectorWrapperWithWasm = class {
|
|
945
|
-
detector;
|
|
946
|
-
events = /* @__PURE__ */ new Map();
|
|
947
|
-
constructor(config) {
|
|
948
|
-
this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true);
|
|
949
|
-
if (config?.baseUrl) {
|
|
950
|
-
this.detector.setBaseUrl(config.baseUrl);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
setBaseUrl(url) {
|
|
954
|
-
this.detector.setBaseUrl(url);
|
|
955
|
-
}
|
|
956
|
-
async analyze(input) {
|
|
957
|
-
const result = await this.detector.analyze(input);
|
|
958
|
-
if (result.isAgent && this.events.has("agent.detected")) {
|
|
959
|
-
const handlers = this.events.get("agent.detected") || [];
|
|
960
|
-
handlers.forEach((handler) => handler(result, input));
|
|
961
|
-
}
|
|
962
|
-
return result;
|
|
963
|
-
}
|
|
964
|
-
on(event, handler) {
|
|
965
|
-
if (!this.events.has(event)) {
|
|
966
|
-
this.events.set(event, []);
|
|
967
|
-
}
|
|
968
|
-
this.events.get(event).push(handler);
|
|
969
|
-
}
|
|
970
|
-
emit(event, ...args) {
|
|
971
|
-
const handlers = this.events.get(event) || [];
|
|
972
|
-
handlers.forEach((handler) => handler(...args));
|
|
973
|
-
}
|
|
974
|
-
async init() {
|
|
975
|
-
await this.detector.init();
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
// src/session-tracker.ts
|
|
980
|
-
var EdgeSessionTracker = class {
|
|
981
|
-
config;
|
|
982
|
-
constructor(config) {
|
|
983
|
-
this.config = {
|
|
984
|
-
enabled: config.enabled,
|
|
985
|
-
cookieName: config.cookieName || "__agentshield_session",
|
|
986
|
-
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
987
|
-
// 1 hour default
|
|
988
|
-
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
/**
|
|
992
|
-
* Track a new AI agent session
|
|
993
|
-
*/
|
|
994
|
-
async track(_request, response, result) {
|
|
995
|
-
try {
|
|
996
|
-
if (!this.config.enabled || !result.isAgent) {
|
|
997
|
-
return response;
|
|
998
|
-
}
|
|
999
|
-
const sessionData = {
|
|
1000
|
-
id: crypto.randomUUID(),
|
|
1001
|
-
agent: result.detectedAgent?.name || "unknown",
|
|
1002
|
-
confidence: result.confidence,
|
|
1003
|
-
detectedAt: Date.now(),
|
|
1004
|
-
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
1005
|
-
};
|
|
1006
|
-
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
1007
|
-
response.cookies.set(this.config.cookieName, encrypted, {
|
|
1008
|
-
httpOnly: true,
|
|
1009
|
-
secure: process.env.NODE_ENV === "production",
|
|
1010
|
-
sameSite: "lax",
|
|
1011
|
-
maxAge: this.config.cookieMaxAge,
|
|
1012
|
-
path: "/"
|
|
1013
|
-
});
|
|
1014
|
-
return response;
|
|
1015
|
-
} catch (error) {
|
|
1016
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
1017
|
-
console.warn("AgentShield: Failed to track session:", error);
|
|
1018
|
-
}
|
|
1019
|
-
return response;
|
|
1036
|
+
if (!this.config.enabled || !shouldEnforce(result)) {
|
|
1037
|
+
return response;
|
|
1038
|
+
}
|
|
1039
|
+
const sessionData = {
|
|
1040
|
+
id: crypto.randomUUID(),
|
|
1041
|
+
agent: result.detectedAgent?.name || "unknown",
|
|
1042
|
+
confidence: result.confidence,
|
|
1043
|
+
detectedAt: Date.now(),
|
|
1044
|
+
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
1045
|
+
};
|
|
1046
|
+
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
1047
|
+
response.cookies.set(this.config.cookieName, encrypted, {
|
|
1048
|
+
httpOnly: true,
|
|
1049
|
+
secure: process.env.NODE_ENV === "production",
|
|
1050
|
+
sameSite: "lax",
|
|
1051
|
+
maxAge: this.config.cookieMaxAge,
|
|
1052
|
+
path: "/"
|
|
1053
|
+
});
|
|
1054
|
+
return response;
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
1057
|
+
console.warn("AgentShield: Failed to track session:", error);
|
|
1058
|
+
}
|
|
1059
|
+
return response;
|
|
1020
1060
|
}
|
|
1021
1061
|
}
|
|
1022
1062
|
/**
|
|
@@ -1068,9 +1108,7 @@ var EdgeSessionTracker = class {
|
|
|
1068
1108
|
for (let i = 0; i < encoded.length; i++) {
|
|
1069
1109
|
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
1070
1110
|
}
|
|
1071
|
-
return btoa(
|
|
1072
|
-
Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
|
|
1073
|
-
);
|
|
1111
|
+
return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
|
|
1074
1112
|
} catch (error) {
|
|
1075
1113
|
return btoa(data);
|
|
1076
1114
|
}
|
|
@@ -1093,14 +1131,29 @@ var EdgeSessionTracker = class {
|
|
|
1093
1131
|
}
|
|
1094
1132
|
};
|
|
1095
1133
|
|
|
1096
|
-
// src/
|
|
1134
|
+
// src/utils.ts
|
|
1135
|
+
function getClientIp(request) {
|
|
1136
|
+
const forwardedFor = request.headers.get("x-forwarded-for");
|
|
1137
|
+
if (forwardedFor) {
|
|
1138
|
+
const ip = forwardedFor.split(",")[0]?.trim();
|
|
1139
|
+
if (ip) return ip;
|
|
1140
|
+
}
|
|
1141
|
+
const realIp = request.headers.get("x-real-ip");
|
|
1142
|
+
if (realIp) return realIp;
|
|
1143
|
+
const cfIp = request.headers.get("cf-connecting-ip");
|
|
1144
|
+
if (cfIp) return cfIp;
|
|
1145
|
+
const clientIp = request.headers.get("x-client-ip");
|
|
1146
|
+
if (clientIp) return clientIp;
|
|
1147
|
+
return void 0;
|
|
1148
|
+
}
|
|
1097
1149
|
function createAgentShieldMiddleware(config = {}) {
|
|
1098
|
-
|
|
1150
|
+
let detector = config.enableWasm ? null : new EdgeAgentDetectorWrapper(config);
|
|
1151
|
+
let detectorInitPromise = null;
|
|
1099
1152
|
const sessionTracker = config.sessionTracking?.enabled || config.enableWasm ? new EdgeSessionTracker({
|
|
1100
1153
|
enabled: true,
|
|
1101
1154
|
...config.sessionTracking
|
|
1102
1155
|
}) : null;
|
|
1103
|
-
if (config.events) {
|
|
1156
|
+
if (detector && config.events) {
|
|
1104
1157
|
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1105
1158
|
if (handler) {
|
|
1106
1159
|
detector.on(event, handler);
|
|
@@ -1121,6 +1174,23 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1121
1174
|
} = config;
|
|
1122
1175
|
return async (request) => {
|
|
1123
1176
|
try {
|
|
1177
|
+
if (!detector) {
|
|
1178
|
+
if (!detectorInitPromise) {
|
|
1179
|
+
detectorInitPromise = (async () => {
|
|
1180
|
+
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1181
|
+
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1182
|
+
if (config.events) {
|
|
1183
|
+
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1184
|
+
if (handler) {
|
|
1185
|
+
detector.on(event, handler);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
})();
|
|
1190
|
+
}
|
|
1191
|
+
await detectorInitPromise;
|
|
1192
|
+
}
|
|
1193
|
+
const activeDetector = detector;
|
|
1124
1194
|
const shouldSkip = skipPaths.some((pattern) => {
|
|
1125
1195
|
if (typeof pattern === "string") {
|
|
1126
1196
|
return request.nextUrl.pathname.startsWith(pattern);
|
|
@@ -1134,11 +1204,11 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1134
1204
|
const existingSession = sessionTracker ? await sessionTracker.check(request) : null;
|
|
1135
1205
|
if (existingSession) {
|
|
1136
1206
|
const response2 = NextResponse.next();
|
|
1137
|
-
response2.headers.set("
|
|
1138
|
-
response2.headers.set("
|
|
1139
|
-
response2.headers.set("
|
|
1140
|
-
response2.headers.set("
|
|
1141
|
-
response2.headers.set("
|
|
1207
|
+
response2.headers.set("kya-detected", "true");
|
|
1208
|
+
response2.headers.set("kya-agent", existingSession.agent);
|
|
1209
|
+
response2.headers.set("kya-confidence", existingSession.confidence.toString());
|
|
1210
|
+
response2.headers.set("kya-session", "continued");
|
|
1211
|
+
response2.headers.set("kya-session-id", existingSession.id);
|
|
1142
1212
|
request.agentShield = {
|
|
1143
1213
|
result: {
|
|
1144
1214
|
isAgent: true,
|
|
@@ -1158,17 +1228,17 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1158
1228
|
};
|
|
1159
1229
|
const context2 = {
|
|
1160
1230
|
userAgent: request.headers.get("user-agent") || "",
|
|
1161
|
-
ipAddress: (request
|
|
1231
|
+
ipAddress: getClientIp(request) || "",
|
|
1162
1232
|
headers: Object.fromEntries(request.headers.entries()),
|
|
1163
1233
|
url: request.url,
|
|
1164
1234
|
method: request.method,
|
|
1165
1235
|
timestamp: /* @__PURE__ */ new Date()
|
|
1166
1236
|
};
|
|
1167
|
-
|
|
1237
|
+
activeDetector.emit("agent.session.continued", existingSession, context2);
|
|
1168
1238
|
return response2;
|
|
1169
1239
|
}
|
|
1170
1240
|
const userAgent = request.headers.get("user-agent");
|
|
1171
|
-
const ipAddress = request
|
|
1241
|
+
const ipAddress = getClientIp(request);
|
|
1172
1242
|
const url = new URL(request.url);
|
|
1173
1243
|
const pathWithQuery = url.pathname + url.search;
|
|
1174
1244
|
const context = {
|
|
@@ -1180,15 +1250,19 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1180
1250
|
method: request.method,
|
|
1181
1251
|
timestamp: /* @__PURE__ */ new Date()
|
|
1182
1252
|
};
|
|
1183
|
-
const result = await
|
|
1184
|
-
|
|
1253
|
+
const result = await activeDetector.analyze(context);
|
|
1254
|
+
const decision = evaluateEnforcement(result, {
|
|
1255
|
+
confidenceThreshold: config.confidenceThreshold,
|
|
1256
|
+
defaultAction: onAgentDetected
|
|
1257
|
+
});
|
|
1258
|
+
if (decision.shouldNotify) {
|
|
1185
1259
|
if (onDetection) {
|
|
1186
1260
|
const customResponse = await onDetection(request, result);
|
|
1187
1261
|
if (customResponse) {
|
|
1188
1262
|
return customResponse;
|
|
1189
1263
|
}
|
|
1190
1264
|
}
|
|
1191
|
-
switch (
|
|
1265
|
+
switch (decision.action) {
|
|
1192
1266
|
case "block": {
|
|
1193
1267
|
const response2 = NextResponse.json(
|
|
1194
1268
|
{
|
|
@@ -1204,11 +1278,17 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1204
1278
|
response2.headers.set(key, value);
|
|
1205
1279
|
});
|
|
1206
1280
|
}
|
|
1207
|
-
|
|
1281
|
+
activeDetector.emit("agent.blocked", result, context);
|
|
1208
1282
|
return response2;
|
|
1209
1283
|
}
|
|
1210
|
-
case "redirect":
|
|
1211
|
-
|
|
1284
|
+
case "redirect": {
|
|
1285
|
+
const redirectTarget = new URL(redirectUrl, request.url);
|
|
1286
|
+
const agentName = result.detectedAgent?.name;
|
|
1287
|
+
if (agentName && !redirectTarget.searchParams.has("agent")) {
|
|
1288
|
+
redirectTarget.searchParams.set("agent", agentName.toLowerCase());
|
|
1289
|
+
}
|
|
1290
|
+
return NextResponse.redirect(redirectTarget);
|
|
1291
|
+
}
|
|
1212
1292
|
case "rewrite":
|
|
1213
1293
|
return NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1214
1294
|
case "log":
|
|
@@ -1224,9 +1304,7 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1224
1304
|
break;
|
|
1225
1305
|
case "allow":
|
|
1226
1306
|
default:
|
|
1227
|
-
|
|
1228
|
-
detector.emit("agent.allowed", result, context);
|
|
1229
|
-
}
|
|
1307
|
+
activeDetector.emit("agent.allowed", result, context);
|
|
1230
1308
|
break;
|
|
1231
1309
|
}
|
|
1232
1310
|
}
|
|
@@ -1235,15 +1313,15 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1235
1313
|
skipped: false
|
|
1236
1314
|
};
|
|
1237
1315
|
let response = NextResponse.next();
|
|
1238
|
-
response.headers.set("
|
|
1239
|
-
response.headers.set("
|
|
1316
|
+
response.headers.set("kya-detected", result.isAgent.toString());
|
|
1317
|
+
response.headers.set("kya-confidence", result.confidence.toString());
|
|
1240
1318
|
if (result.detectedAgent?.name) {
|
|
1241
|
-
response.headers.set("
|
|
1319
|
+
response.headers.set("kya-agent", result.detectedAgent.name);
|
|
1242
1320
|
}
|
|
1243
|
-
if (sessionTracker &&
|
|
1321
|
+
if (sessionTracker && decision.shouldNotify) {
|
|
1244
1322
|
response = await sessionTracker.track(request, response, result);
|
|
1245
|
-
response.headers.set("
|
|
1246
|
-
|
|
1323
|
+
response.headers.set("kya-session", "new");
|
|
1324
|
+
activeDetector.emit("agent.session.started", result, context);
|
|
1247
1325
|
}
|
|
1248
1326
|
return response;
|
|
1249
1327
|
} catch (error) {
|