@kya-os/agentshield-nextjs 0.1.41 → 0.1.43
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 +42 -22
- package/wasm/agentshield_wasm.js +209 -152
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/wasm/package.json +30 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var agentshieldShared = require('@kya-os/agentshield-shared');
|
|
3
4
|
var server = require('next/server');
|
|
5
|
+
var ed25519 = require('@noble/ed25519');
|
|
6
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var ed25519__namespace = /*#__PURE__*/_interopNamespace(ed25519);
|
|
4
27
|
|
|
5
28
|
var __defProp = Object.defineProperty;
|
|
6
29
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -47,26 +70,31 @@ async function initWasm() {
|
|
|
47
70
|
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
48
71
|
}
|
|
49
72
|
const streamResponse = response2.clone();
|
|
50
|
-
const { instance } = await WebAssembly.instantiateStreaming(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.log("WASM:", ptr, len);
|
|
56
|
-
},
|
|
57
|
-
__wbindgen_throw: (ptr, len) => {
|
|
58
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
73
|
+
const { instance } = await WebAssembly.instantiateStreaming(streamResponse, {
|
|
74
|
+
wbg: {
|
|
75
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
76
|
+
if (process.env.NODE_ENV !== "production") {
|
|
77
|
+
console.debug("WASM:", ptr, len);
|
|
59
78
|
}
|
|
79
|
+
},
|
|
80
|
+
__wbindgen_throw: (ptr, len) => {
|
|
81
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
|
-
);
|
|
84
|
+
});
|
|
63
85
|
wasmInstance = instance;
|
|
64
86
|
wasmExports = instance.exports;
|
|
65
|
-
|
|
87
|
+
if (process.env.NODE_ENV !== "production") {
|
|
88
|
+
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
89
|
+
}
|
|
66
90
|
return;
|
|
67
91
|
} catch (streamError) {
|
|
68
92
|
if (!controller.signal.aborted) {
|
|
69
|
-
|
|
93
|
+
if (process.env.NODE_ENV !== "production") {
|
|
94
|
+
console.debug(
|
|
95
|
+
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
70
98
|
} else {
|
|
71
99
|
throw streamError;
|
|
72
100
|
}
|
|
@@ -82,7 +110,9 @@ async function initWasm() {
|
|
|
82
110
|
const imports = {
|
|
83
111
|
wbg: {
|
|
84
112
|
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
85
|
-
|
|
113
|
+
if (process.env.NODE_ENV !== "production") {
|
|
114
|
+
console.debug("WASM:", ptr, len);
|
|
115
|
+
}
|
|
86
116
|
},
|
|
87
117
|
__wbindgen_throw: (ptr, len) => {
|
|
88
118
|
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
@@ -91,12 +121,20 @@ async function initWasm() {
|
|
|
91
121
|
};
|
|
92
122
|
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
93
123
|
wasmExports = wasmInstance.exports;
|
|
94
|
-
|
|
124
|
+
if (process.env.NODE_ENV !== "production") {
|
|
125
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
126
|
+
}
|
|
95
127
|
} catch (fetchError) {
|
|
96
|
-
|
|
97
|
-
|
|
128
|
+
const error = fetchError;
|
|
129
|
+
if (error.name === "AbortError") {
|
|
130
|
+
console.warn(
|
|
131
|
+
"[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection"
|
|
132
|
+
);
|
|
98
133
|
} else {
|
|
99
|
-
console.warn(
|
|
134
|
+
console.warn(
|
|
135
|
+
"[AgentShield] Failed to fetch WASM file:",
|
|
136
|
+
error.message || "Unknown error"
|
|
137
|
+
);
|
|
100
138
|
}
|
|
101
139
|
wasmExports = null;
|
|
102
140
|
}
|
|
@@ -174,32 +212,20 @@ __export(edge_detector_with_wasm_exports, {
|
|
|
174
212
|
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
175
213
|
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
176
214
|
});
|
|
177
|
-
var
|
|
215
|
+
var rules2, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
178
216
|
var init_edge_detector_with_wasm = __esm({
|
|
179
217
|
"src/edge-detector-with-wasm.ts"() {
|
|
180
218
|
init_wasm_loader();
|
|
181
|
-
|
|
182
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
183
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
184
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
185
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
186
|
-
{ pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
|
|
187
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
188
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
189
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
190
|
-
];
|
|
191
|
-
CLOUD_PROVIDERS2 = {
|
|
192
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
193
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
194
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
195
|
-
};
|
|
219
|
+
rules2 = agentshieldShared.loadRulesSync();
|
|
196
220
|
EdgeAgentDetectorWithWasm = class {
|
|
197
221
|
constructor(enableWasm = true) {
|
|
198
222
|
this.enableWasm = enableWasm;
|
|
223
|
+
this.rules = rules2;
|
|
199
224
|
}
|
|
200
225
|
wasmEnabled = false;
|
|
201
226
|
initPromise = null;
|
|
202
227
|
baseUrl = null;
|
|
228
|
+
rules;
|
|
203
229
|
/**
|
|
204
230
|
* Set the base URL for WASM loading in Edge Runtime
|
|
205
231
|
*/
|
|
@@ -222,11 +248,14 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
222
248
|
this.initPromise = (async () => {
|
|
223
249
|
try {
|
|
224
250
|
const wasmAvailable = await isWasmAvailable();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
if (wasmAvailable) {
|
|
252
|
+
if (this.baseUrl) {
|
|
253
|
+
setWasmBaseUrl(this.baseUrl);
|
|
254
|
+
}
|
|
255
|
+
await initWasm();
|
|
256
|
+
this.wasmEnabled = true;
|
|
228
257
|
} else {
|
|
229
|
-
|
|
258
|
+
this.wasmEnabled = false;
|
|
230
259
|
}
|
|
231
260
|
} catch (error) {
|
|
232
261
|
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
@@ -251,69 +280,84 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
251
280
|
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
252
281
|
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
253
282
|
if (signatureAgent?.includes("chatgpt.com")) {
|
|
254
|
-
confidence =
|
|
283
|
+
confidence = 85;
|
|
255
284
|
reasons.push("signature_agent:chatgpt");
|
|
256
285
|
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
257
286
|
verificationMethod = "signature";
|
|
258
287
|
} else if (signaturePresent) {
|
|
259
|
-
confidence = Math.max(confidence,
|
|
288
|
+
confidence = Math.max(confidence, 40);
|
|
260
289
|
reasons.push("signature_present");
|
|
261
290
|
}
|
|
262
291
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
263
292
|
if (userAgent) {
|
|
264
|
-
for (const
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
reasons.push(`known_pattern:${type}`);
|
|
293
|
+
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
294
|
+
const matched = agentRule.patterns.some((pattern) => {
|
|
295
|
+
const regex = new RegExp(pattern, "i");
|
|
296
|
+
return regex.test(userAgent);
|
|
297
|
+
});
|
|
298
|
+
if (matched) {
|
|
299
|
+
const agentType = this.getAgentType(agentKey);
|
|
300
|
+
const agentName = this.getAgentName(agentKey);
|
|
301
|
+
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
302
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
275
303
|
if (!detectedAgent) {
|
|
276
|
-
detectedAgent = { type, name };
|
|
304
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
277
305
|
verificationMethod = "pattern";
|
|
278
306
|
}
|
|
279
307
|
break;
|
|
280
308
|
}
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
"anthropic-client-id",
|
|
287
|
-
"x-goog-api-client",
|
|
288
|
-
"x-ms-copilot-id"
|
|
289
|
-
];
|
|
290
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
291
|
-
(header) => normalizedHeaders[header]
|
|
311
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
312
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
313
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
292
314
|
);
|
|
293
315
|
if (foundAiHeaders.length > 0) {
|
|
294
|
-
|
|
316
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
317
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
295
318
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
296
319
|
}
|
|
297
320
|
const ip = input.ip || input.ipAddress;
|
|
298
321
|
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
322
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
323
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
324
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
325
|
+
continue;
|
|
326
|
+
const matched = ipRule.ranges.some((range) => {
|
|
327
|
+
const prefix = range.split("/")[0];
|
|
328
|
+
const prefixParts = prefix.split(".");
|
|
329
|
+
const ipParts = ip.split(".");
|
|
330
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
331
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
});
|
|
337
|
+
if (matched) {
|
|
338
|
+
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
302
339
|
reasons.push(`cloud_provider:${provider}`);
|
|
303
340
|
break;
|
|
304
341
|
}
|
|
305
342
|
}
|
|
306
343
|
}
|
|
307
344
|
if (reasons.length > 2) {
|
|
308
|
-
confidence = Math.min(confidence * 1.2,
|
|
345
|
+
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
309
346
|
}
|
|
347
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
310
348
|
return {
|
|
311
|
-
isAgent: confidence >
|
|
349
|
+
isAgent: confidence > 30,
|
|
350
|
+
// 30% threshold
|
|
312
351
|
confidence,
|
|
352
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
353
|
+
signals: [],
|
|
354
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
313
355
|
...detectedAgent && { detectedAgent },
|
|
314
356
|
reasons,
|
|
315
|
-
...verificationMethod && {
|
|
316
|
-
|
|
357
|
+
...verificationMethod && {
|
|
358
|
+
verificationMethod: agentshieldShared.mapVerificationMethod(verificationMethod)
|
|
359
|
+
},
|
|
360
|
+
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
317
361
|
timestamp: /* @__PURE__ */ new Date()
|
|
318
362
|
};
|
|
319
363
|
}
|
|
@@ -330,15 +374,17 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
330
374
|
input.ip || input.ipAddress
|
|
331
375
|
);
|
|
332
376
|
if (wasmResult) {
|
|
333
|
-
console.log("[AgentShield] Using WASM detection result");
|
|
334
377
|
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
335
378
|
return {
|
|
336
379
|
isAgent: wasmResult.isAgent,
|
|
337
380
|
confidence: wasmResult.confidence,
|
|
381
|
+
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
382
|
+
signals: [],
|
|
383
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
338
384
|
...detectedAgent && { detectedAgent },
|
|
339
385
|
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
340
|
-
verificationMethod: wasmResult.verificationMethod,
|
|
341
|
-
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence >
|
|
386
|
+
verificationMethod: agentshieldShared.mapVerificationMethod(wasmResult.verificationMethod),
|
|
387
|
+
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
342
388
|
timestamp: /* @__PURE__ */ new Date()
|
|
343
389
|
};
|
|
344
390
|
}
|
|
@@ -347,15 +393,50 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
347
393
|
}
|
|
348
394
|
}
|
|
349
395
|
const patternResult = await this.patternDetection(input);
|
|
350
|
-
if (this.wasmEnabled && patternResult.confidence >=
|
|
351
|
-
patternResult.confidence = Math.min(
|
|
396
|
+
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
397
|
+
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
352
398
|
patternResult.reasons.push("wasm_enhanced");
|
|
353
|
-
if (!patternResult.verificationMethod) {
|
|
354
|
-
patternResult.verificationMethod = "wasm-enhanced";
|
|
355
|
-
}
|
|
356
399
|
}
|
|
357
400
|
return patternResult;
|
|
358
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Get agent type from rule key
|
|
404
|
+
*/
|
|
405
|
+
getAgentType(agentKey) {
|
|
406
|
+
const typeMap = {
|
|
407
|
+
openai_gptbot: "openai",
|
|
408
|
+
anthropic_claude: "anthropic",
|
|
409
|
+
perplexity_bot: "perplexity",
|
|
410
|
+
google_ai: "google",
|
|
411
|
+
microsoft_ai: "microsoft",
|
|
412
|
+
meta_ai: "meta",
|
|
413
|
+
cohere_bot: "cohere",
|
|
414
|
+
huggingface_bot: "huggingface",
|
|
415
|
+
generic_bot: "generic",
|
|
416
|
+
dev_tools: "dev",
|
|
417
|
+
automation_tools: "automation"
|
|
418
|
+
};
|
|
419
|
+
return typeMap[agentKey] || agentKey;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get agent name from rule key
|
|
423
|
+
*/
|
|
424
|
+
getAgentName(agentKey) {
|
|
425
|
+
const nameMap = {
|
|
426
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
427
|
+
anthropic_claude: "Claude",
|
|
428
|
+
perplexity_bot: "Perplexity",
|
|
429
|
+
google_ai: "Google AI",
|
|
430
|
+
microsoft_ai: "Microsoft Copilot",
|
|
431
|
+
meta_ai: "Meta AI",
|
|
432
|
+
cohere_bot: "Cohere",
|
|
433
|
+
huggingface_bot: "HuggingFace",
|
|
434
|
+
generic_bot: "Generic Bot",
|
|
435
|
+
dev_tools: "Development Tool",
|
|
436
|
+
automation_tools: "Automation Tool"
|
|
437
|
+
};
|
|
438
|
+
return nameMap[agentKey] || agentKey;
|
|
439
|
+
}
|
|
359
440
|
/**
|
|
360
441
|
* Map agent names from WASM to consistent format
|
|
361
442
|
*/
|
|
@@ -434,19 +515,19 @@ async function checkWasmAvailability() {
|
|
|
434
515
|
}
|
|
435
516
|
}
|
|
436
517
|
function shouldIndicateWasmVerification(confidence) {
|
|
437
|
-
return confidence >=
|
|
518
|
+
return confidence >= 85 && confidence < 100;
|
|
438
519
|
}
|
|
439
520
|
function getWasmConfidenceBoost(baseConfidence, reasons = []) {
|
|
440
521
|
if (reasons.some(
|
|
441
522
|
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
442
523
|
)) {
|
|
443
|
-
return
|
|
524
|
+
return 100;
|
|
444
525
|
}
|
|
445
|
-
if (baseConfidence >=
|
|
446
|
-
return
|
|
526
|
+
if (baseConfidence >= 85) {
|
|
527
|
+
return 95;
|
|
447
528
|
}
|
|
448
|
-
if (baseConfidence >=
|
|
449
|
-
return Math.min(baseConfidence * 1.1,
|
|
529
|
+
if (baseConfidence >= 70) {
|
|
530
|
+
return Math.min(baseConfidence * 1.1, 90);
|
|
450
531
|
}
|
|
451
532
|
return baseConfidence;
|
|
452
533
|
}
|
|
@@ -465,19 +546,126 @@ var init_wasm_confidence = __esm({
|
|
|
465
546
|
"src/wasm-confidence.ts"() {
|
|
466
547
|
}
|
|
467
548
|
});
|
|
468
|
-
|
|
469
|
-
// src/signature-verifier.ts
|
|
549
|
+
ed25519__namespace.etc.sha512Sync = (...m) => sha2_js.sha512(ed25519__namespace.etc.concatBytes(...m));
|
|
470
550
|
var KNOWN_KEYS = {
|
|
471
551
|
chatgpt: [
|
|
472
552
|
{
|
|
473
553
|
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
474
554
|
// ChatGPT's current Ed25519 public key (base64)
|
|
555
|
+
// Source: https://chatgpt.com/.well-known/http-message-signatures-directory
|
|
475
556
|
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
476
|
-
validFrom:
|
|
477
|
-
|
|
557
|
+
validFrom: 1735689600,
|
|
558
|
+
// Jan 1, 2025 (from OpenAI's nbf)
|
|
559
|
+
// Extended expiration as fallback safety - API fetch should provide fresh keys
|
|
560
|
+
// Check OpenAI's well-known endpoint for actual expiration dates
|
|
561
|
+
validUntil: 1799625600
|
|
562
|
+
// Jan 1, 2027 (extended for fallback safety)
|
|
478
563
|
}
|
|
479
564
|
]
|
|
480
565
|
};
|
|
566
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
567
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
568
|
+
var CACHE_MAX_SIZE = 100;
|
|
569
|
+
function getApiBaseUrl() {
|
|
570
|
+
if (typeof window !== "undefined") {
|
|
571
|
+
return "/api/internal";
|
|
572
|
+
}
|
|
573
|
+
const baseUrl2 = process.env.NEXT_PUBLIC_APP_URL || process.env.NEXT_PUBLIC_API_URL || process.env.API_URL || (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);
|
|
574
|
+
if (baseUrl2) {
|
|
575
|
+
return baseUrl2.replace(/\/$/, "") + "/api/internal";
|
|
576
|
+
}
|
|
577
|
+
if (process.env.NODE_ENV !== "production") {
|
|
578
|
+
console.warn(
|
|
579
|
+
"[Signature] No base URL configured for server-side fetch. Using localhost fallback."
|
|
580
|
+
);
|
|
581
|
+
return "http://localhost:3000/api/internal";
|
|
582
|
+
}
|
|
583
|
+
console.error(
|
|
584
|
+
"[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
|
|
585
|
+
);
|
|
586
|
+
return "/api/internal";
|
|
587
|
+
}
|
|
588
|
+
function cleanupExpiredCache() {
|
|
589
|
+
const now = Date.now();
|
|
590
|
+
const entriesToDelete = [];
|
|
591
|
+
for (const [agent, cached] of keyCache.entries()) {
|
|
592
|
+
if (now - cached.cachedAt > CACHE_TTL_MS) {
|
|
593
|
+
entriesToDelete.push(agent);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
for (const agent of entriesToDelete) {
|
|
597
|
+
keyCache.delete(agent);
|
|
598
|
+
}
|
|
599
|
+
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
600
|
+
const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
|
|
601
|
+
agent,
|
|
602
|
+
cachedAt: cached.cachedAt
|
|
603
|
+
}));
|
|
604
|
+
entries.sort((a, b) => a.cachedAt - b.cachedAt);
|
|
605
|
+
const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
|
|
606
|
+
for (const entry of toRemove) {
|
|
607
|
+
keyCache.delete(entry.agent);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function fetchKeysFromApi(agent) {
|
|
612
|
+
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
613
|
+
cleanupExpiredCache();
|
|
614
|
+
}
|
|
615
|
+
const cached = keyCache.get(agent);
|
|
616
|
+
if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
|
|
617
|
+
return cached.keys;
|
|
618
|
+
}
|
|
619
|
+
if (typeof fetch === "undefined") {
|
|
620
|
+
console.warn("[Signature] fetch() not available in this environment");
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
625
|
+
const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
|
|
626
|
+
const response = await fetch(url, {
|
|
627
|
+
method: "GET",
|
|
628
|
+
headers: {
|
|
629
|
+
"Content-Type": "application/json"
|
|
630
|
+
},
|
|
631
|
+
// 5 second timeout
|
|
632
|
+
signal: AbortSignal.timeout(5e3)
|
|
633
|
+
});
|
|
634
|
+
if (!response.ok) {
|
|
635
|
+
console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
const data = await response.json();
|
|
639
|
+
if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
|
|
640
|
+
console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
keyCache.set(agent, {
|
|
644
|
+
keys: data.keys,
|
|
645
|
+
cachedAt: Date.now()
|
|
646
|
+
});
|
|
647
|
+
return data.keys;
|
|
648
|
+
} catch (error) {
|
|
649
|
+
console.warn("[Signature] Error fetching keys from API, using fallback", {
|
|
650
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
651
|
+
agent
|
|
652
|
+
});
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function isValidAgent(agent) {
|
|
657
|
+
return agent in KNOWN_KEYS;
|
|
658
|
+
}
|
|
659
|
+
async function getKeysForAgent(agent) {
|
|
660
|
+
const apiKeys = await fetchKeysFromApi(agent);
|
|
661
|
+
if (apiKeys && apiKeys.length > 0) {
|
|
662
|
+
return apiKeys;
|
|
663
|
+
}
|
|
664
|
+
if (isValidAgent(agent)) {
|
|
665
|
+
return KNOWN_KEYS[agent];
|
|
666
|
+
}
|
|
667
|
+
return [];
|
|
668
|
+
}
|
|
481
669
|
function parseSignatureInput(signatureInput) {
|
|
482
670
|
try {
|
|
483
671
|
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
@@ -513,21 +701,29 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
|
513
701
|
case "@authority":
|
|
514
702
|
value = headers["host"] || headers["Host"] || "";
|
|
515
703
|
break;
|
|
516
|
-
default:
|
|
517
|
-
const key = Object.keys(headers).find(
|
|
518
|
-
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
519
|
-
);
|
|
704
|
+
default: {
|
|
705
|
+
const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
|
|
520
706
|
value = key ? headers[key] || "" : "";
|
|
521
707
|
break;
|
|
708
|
+
}
|
|
522
709
|
}
|
|
523
710
|
components.push(`"${headerName}": ${value}`);
|
|
524
711
|
}
|
|
525
712
|
return components.join("\n");
|
|
526
713
|
}
|
|
714
|
+
function base64ToBytes(base64) {
|
|
715
|
+
let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
716
|
+
const padding = standardBase64.length % 4;
|
|
717
|
+
if (padding) {
|
|
718
|
+
standardBase64 += "=".repeat(4 - padding);
|
|
719
|
+
}
|
|
720
|
+
const binaryString = atob(standardBase64);
|
|
721
|
+
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
722
|
+
}
|
|
527
723
|
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
528
724
|
try {
|
|
529
|
-
const publicKeyBytes =
|
|
530
|
-
const signatureBytes =
|
|
725
|
+
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
726
|
+
const signatureBytes = base64ToBytes(signatureBase64);
|
|
531
727
|
const messageBytes = new TextEncoder().encode(message);
|
|
532
728
|
if (publicKeyBytes.length !== 32) {
|
|
533
729
|
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
@@ -537,34 +733,36 @@ async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message)
|
|
|
537
733
|
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
538
734
|
return false;
|
|
539
735
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
736
|
+
return ed25519__namespace.verify(signatureBytes, messageBytes, publicKeyBytes);
|
|
737
|
+
} catch (nobleError) {
|
|
738
|
+
console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
|
|
739
|
+
try {
|
|
740
|
+
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
741
|
+
const signatureBytes = base64ToBytes(signatureBase64);
|
|
742
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
743
|
+
const publicKey = await crypto.subtle.importKey(
|
|
744
|
+
"raw",
|
|
745
|
+
publicKeyBytes.buffer,
|
|
746
|
+
{
|
|
747
|
+
name: "Ed25519",
|
|
748
|
+
namedCurve: "Ed25519"
|
|
749
|
+
},
|
|
750
|
+
false,
|
|
751
|
+
["verify"]
|
|
752
|
+
);
|
|
753
|
+
return await crypto.subtle.verify(
|
|
754
|
+
"Ed25519",
|
|
755
|
+
publicKey,
|
|
756
|
+
signatureBytes.buffer,
|
|
757
|
+
messageBytes
|
|
758
|
+
);
|
|
759
|
+
} catch (cryptoError) {
|
|
760
|
+
console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
|
|
761
|
+
nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
|
|
762
|
+
cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
|
|
763
|
+
});
|
|
764
|
+
return false;
|
|
566
765
|
}
|
|
567
|
-
return false;
|
|
568
766
|
}
|
|
569
767
|
}
|
|
570
768
|
async function verifyAgentSignature(method, path, headers) {
|
|
@@ -609,12 +807,12 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
609
807
|
}
|
|
610
808
|
}
|
|
611
809
|
let agent;
|
|
612
|
-
let
|
|
810
|
+
let agentKey;
|
|
613
811
|
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
614
812
|
agent = "ChatGPT";
|
|
615
|
-
|
|
813
|
+
agentKey = "chatgpt";
|
|
616
814
|
}
|
|
617
|
-
if (!agent || !
|
|
815
|
+
if (!agent || !agentKey) {
|
|
618
816
|
return {
|
|
619
817
|
isValid: false,
|
|
620
818
|
confidence: 0,
|
|
@@ -622,6 +820,15 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
622
820
|
verificationMethod: "none"
|
|
623
821
|
};
|
|
624
822
|
}
|
|
823
|
+
const knownKeys = await getKeysForAgent(agentKey);
|
|
824
|
+
if (knownKeys.length === 0) {
|
|
825
|
+
return {
|
|
826
|
+
isValid: false,
|
|
827
|
+
confidence: 0,
|
|
828
|
+
reason: "No keys available for agent",
|
|
829
|
+
verificationMethod: "none"
|
|
830
|
+
};
|
|
831
|
+
}
|
|
625
832
|
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
626
833
|
if (!key) {
|
|
627
834
|
return {
|
|
@@ -648,11 +855,7 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
648
855
|
if (signatureValue.endsWith(":")) {
|
|
649
856
|
signatureValue = signatureValue.slice(0, -1);
|
|
650
857
|
}
|
|
651
|
-
const isValid = await verifyEd25519Signature(
|
|
652
|
-
key.publicKey,
|
|
653
|
-
signatureValue,
|
|
654
|
-
signatureBase
|
|
655
|
-
);
|
|
858
|
+
const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
|
|
656
859
|
if (isValid) {
|
|
657
860
|
return {
|
|
658
861
|
isValid: true,
|
|
@@ -676,27 +879,27 @@ function hasSignatureHeaders(headers) {
|
|
|
676
879
|
}
|
|
677
880
|
function isChatGPTSignature(headers) {
|
|
678
881
|
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
679
|
-
|
|
882
|
+
if (!signatureAgent) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
|
|
886
|
+
if (agentUrlStr === "https://chatgpt.com") {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
const agentUrl = new URL(agentUrlStr);
|
|
891
|
+
const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
|
|
892
|
+
return allowedHosts.includes(agentUrl.host);
|
|
893
|
+
} catch {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
680
896
|
}
|
|
681
|
-
|
|
682
|
-
// src/edge-detector-wrapper.ts
|
|
683
|
-
var AI_AGENT_PATTERNS = [
|
|
684
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
685
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
686
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
687
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
688
|
-
{ pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
|
|
689
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
690
|
-
// Fallback
|
|
691
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
692
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
693
|
-
];
|
|
694
|
-
var CLOUD_PROVIDERS = {
|
|
695
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
696
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
697
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
698
|
-
};
|
|
897
|
+
var rules = agentshieldShared.loadRulesSync();
|
|
699
898
|
var EdgeAgentDetector = class {
|
|
899
|
+
rules;
|
|
900
|
+
constructor() {
|
|
901
|
+
this.rules = rules;
|
|
902
|
+
}
|
|
700
903
|
async analyze(input) {
|
|
701
904
|
const reasons = [];
|
|
702
905
|
let detectedAgent;
|
|
@@ -715,7 +918,7 @@ var EdgeAgentDetector = class {
|
|
|
715
918
|
headers
|
|
716
919
|
);
|
|
717
920
|
if (signatureResult.isValid) {
|
|
718
|
-
confidence = signatureResult.confidence;
|
|
921
|
+
confidence = signatureResult.confidence * 100;
|
|
719
922
|
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
720
923
|
if (signatureResult.agent) {
|
|
721
924
|
detectedAgent = {
|
|
@@ -728,7 +931,13 @@ var EdgeAgentDetector = class {
|
|
|
728
931
|
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
729
932
|
}
|
|
730
933
|
} else {
|
|
731
|
-
|
|
934
|
+
console.warn("[EdgeAgentDetector] Signature verification failed:", {
|
|
935
|
+
reason: signatureResult.reason,
|
|
936
|
+
agent: signatureResult.agent,
|
|
937
|
+
hasSignatureAgent: !!headers["signature-agent"] || !!headers["Signature-Agent"],
|
|
938
|
+
signatureAgentValue: headers["signature-agent"] || headers["Signature-Agent"]
|
|
939
|
+
});
|
|
940
|
+
confidence = Math.max(confidence, 30);
|
|
732
941
|
reasons.push("invalid_signature");
|
|
733
942
|
if (signatureResult.reason) {
|
|
734
943
|
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
@@ -740,68 +949,133 @@ var EdgeAgentDetector = class {
|
|
|
740
949
|
}
|
|
741
950
|
} catch (error) {
|
|
742
951
|
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
743
|
-
confidence = Math.max(confidence,
|
|
952
|
+
confidence = Math.max(confidence, 20);
|
|
744
953
|
reasons.push("signature_verification_error");
|
|
745
954
|
}
|
|
746
955
|
}
|
|
747
956
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
748
957
|
if (userAgent) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
958
|
+
const userAgentEntries = Object.entries(this.rules.rules.userAgents);
|
|
959
|
+
const genericKeys = ["generic_bot", "dev_tools", "automation_tools"];
|
|
960
|
+
const sortedEntries = userAgentEntries.sort((a, b) => {
|
|
961
|
+
const aIsGeneric = genericKeys.includes(a[0]);
|
|
962
|
+
const bIsGeneric = genericKeys.includes(b[0]);
|
|
963
|
+
if (aIsGeneric && !bIsGeneric) return 1;
|
|
964
|
+
if (!aIsGeneric && bIsGeneric) return -1;
|
|
965
|
+
return 0;
|
|
966
|
+
});
|
|
967
|
+
for (const [agentKey, agentRule] of sortedEntries) {
|
|
968
|
+
const rule = agentRule;
|
|
969
|
+
const matched = rule.patterns.some((pattern) => {
|
|
970
|
+
const regex = new RegExp(pattern, "i");
|
|
971
|
+
return regex.test(userAgent);
|
|
972
|
+
});
|
|
973
|
+
if (matched) {
|
|
974
|
+
const agentType = this.getAgentType(agentKey);
|
|
975
|
+
const agentName = this.getAgentName(agentKey);
|
|
976
|
+
confidence = Math.max(confidence, rule.confidence * 100);
|
|
977
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
760
978
|
if (!detectedAgent) {
|
|
761
|
-
detectedAgent = { type, name };
|
|
979
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
762
980
|
verificationMethod = "pattern";
|
|
763
981
|
}
|
|
764
982
|
break;
|
|
765
983
|
}
|
|
766
984
|
}
|
|
767
985
|
}
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
"anthropic-client-id",
|
|
772
|
-
"x-goog-api-client",
|
|
773
|
-
"x-ms-copilot-id"
|
|
774
|
-
];
|
|
775
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
776
|
-
(header) => normalizedHeaders[header]
|
|
986
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
987
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
988
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
777
989
|
);
|
|
778
990
|
if (foundAiHeaders.length > 0) {
|
|
779
|
-
|
|
991
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence * 100));
|
|
992
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
780
993
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
781
994
|
}
|
|
782
995
|
const ip = input.ip || input.ipAddress;
|
|
783
996
|
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
997
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
998
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
999
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
1000
|
+
continue;
|
|
1001
|
+
const matched = ipRule.ranges.some((range) => {
|
|
1002
|
+
const prefix = range.split("/")[0];
|
|
1003
|
+
const prefixParts = prefix.split(".");
|
|
1004
|
+
const ipParts = ip.split(".");
|
|
1005
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
1006
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return true;
|
|
1011
|
+
});
|
|
1012
|
+
if (matched) {
|
|
1013
|
+
const rule = ipRule;
|
|
1014
|
+
confidence = Math.max(confidence, rule.confidence * 40);
|
|
787
1015
|
reasons.push(`cloud_provider:${provider}`);
|
|
788
1016
|
break;
|
|
789
1017
|
}
|
|
790
1018
|
}
|
|
791
1019
|
}
|
|
792
|
-
if (reasons.length > 2 && confidence <
|
|
793
|
-
confidence = Math.min(confidence * 1.2,
|
|
1020
|
+
if (reasons.length > 2 && confidence < 100) {
|
|
1021
|
+
confidence = Math.min(confidence * 1.2, 95);
|
|
794
1022
|
}
|
|
1023
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
795
1024
|
return {
|
|
796
|
-
isAgent: confidence >
|
|
1025
|
+
isAgent: confidence > 30,
|
|
1026
|
+
// Updated to 0-100 scale (was 0.3)
|
|
797
1027
|
confidence,
|
|
1028
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1029
|
+
signals: [],
|
|
1030
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
798
1031
|
...detectedAgent && { detectedAgent },
|
|
799
1032
|
reasons,
|
|
800
|
-
...verificationMethod && {
|
|
801
|
-
|
|
1033
|
+
...verificationMethod && {
|
|
1034
|
+
verificationMethod
|
|
1035
|
+
},
|
|
1036
|
+
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 80 ? "medium" : "high",
|
|
1037
|
+
// Updated to 0-100 scale
|
|
802
1038
|
timestamp: /* @__PURE__ */ new Date()
|
|
803
1039
|
};
|
|
804
1040
|
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Get agent type from rule key
|
|
1043
|
+
*/
|
|
1044
|
+
getAgentType(agentKey) {
|
|
1045
|
+
const typeMap = {
|
|
1046
|
+
openai_gptbot: "openai",
|
|
1047
|
+
anthropic_claude: "anthropic",
|
|
1048
|
+
perplexity_bot: "perplexity",
|
|
1049
|
+
google_ai: "google",
|
|
1050
|
+
microsoft_ai: "microsoft",
|
|
1051
|
+
meta_ai: "meta",
|
|
1052
|
+
cohere_bot: "cohere",
|
|
1053
|
+
huggingface_bot: "huggingface",
|
|
1054
|
+
generic_bot: "generic",
|
|
1055
|
+
dev_tools: "dev",
|
|
1056
|
+
automation_tools: "automation"
|
|
1057
|
+
};
|
|
1058
|
+
return typeMap[agentKey] || agentKey;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Get agent name from rule key
|
|
1062
|
+
*/
|
|
1063
|
+
getAgentName(agentKey) {
|
|
1064
|
+
const nameMap = {
|
|
1065
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
1066
|
+
anthropic_claude: "Claude",
|
|
1067
|
+
perplexity_bot: "Perplexity",
|
|
1068
|
+
google_ai: "Google AI",
|
|
1069
|
+
microsoft_ai: "Microsoft Copilot",
|
|
1070
|
+
meta_ai: "Meta AI",
|
|
1071
|
+
cohere_bot: "Cohere",
|
|
1072
|
+
huggingface_bot: "HuggingFace",
|
|
1073
|
+
generic_bot: "Generic Bot",
|
|
1074
|
+
dev_tools: "Development Tool",
|
|
1075
|
+
automation_tools: "Automation Tool"
|
|
1076
|
+
};
|
|
1077
|
+
return nameMap[agentKey] || agentKey;
|
|
1078
|
+
}
|
|
805
1079
|
};
|
|
806
1080
|
var EdgeAgentDetectorWrapper = class {
|
|
807
1081
|
detector;
|
|
@@ -1014,7 +1288,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1014
1288
|
}) : null;
|
|
1015
1289
|
if (config.events) {
|
|
1016
1290
|
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1017
|
-
|
|
1291
|
+
if (handler) {
|
|
1292
|
+
detector.on(event, handler);
|
|
1293
|
+
}
|
|
1018
1294
|
});
|
|
1019
1295
|
}
|
|
1020
1296
|
const {
|
|
@@ -1046,19 +1322,22 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1046
1322
|
const response2 = server.NextResponse.next();
|
|
1047
1323
|
response2.headers.set("x-agentshield-detected", "true");
|
|
1048
1324
|
response2.headers.set("x-agentshield-agent", existingSession.agent);
|
|
1049
|
-
response2.headers.set(
|
|
1050
|
-
"x-agentshield-confidence",
|
|
1051
|
-
existingSession.confidence.toString()
|
|
1052
|
-
);
|
|
1325
|
+
response2.headers.set("x-agentshield-confidence", existingSession.confidence.toString());
|
|
1053
1326
|
response2.headers.set("x-agentshield-session", "continued");
|
|
1054
1327
|
response2.headers.set("x-agentshield-session-id", existingSession.id);
|
|
1055
1328
|
request.agentShield = {
|
|
1056
1329
|
result: {
|
|
1057
1330
|
isAgent: true,
|
|
1058
1331
|
confidence: existingSession.confidence,
|
|
1059
|
-
|
|
1332
|
+
detectionClass: { type: "AiAgent" },
|
|
1333
|
+
detectedAgent: {
|
|
1334
|
+
type: "ai_agent",
|
|
1335
|
+
name: existingSession.agent
|
|
1336
|
+
},
|
|
1060
1337
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1061
|
-
verificationMethod: "
|
|
1338
|
+
verificationMethod: "behavioral",
|
|
1339
|
+
reasons: ["Session continued"],
|
|
1340
|
+
signals: []
|
|
1062
1341
|
},
|
|
1063
1342
|
session: existingSession,
|
|
1064
1343
|
skipped: false
|
|
@@ -1088,7 +1367,7 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1088
1367
|
timestamp: /* @__PURE__ */ new Date()
|
|
1089
1368
|
};
|
|
1090
1369
|
const result = await detector.analyze(context);
|
|
1091
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1370
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1092
1371
|
if (onDetection) {
|
|
1093
1372
|
const customResponse = await onDetection(request, result);
|
|
1094
1373
|
if (customResponse) {
|
|
@@ -1107,11 +1386,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1107
1386
|
{ status: blockedResponse.status }
|
|
1108
1387
|
);
|
|
1109
1388
|
if (blockedResponse.headers) {
|
|
1110
|
-
Object.entries(blockedResponse.headers).forEach(
|
|
1111
|
-
(
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
);
|
|
1389
|
+
Object.entries(blockedResponse.headers).forEach(([key, value]) => {
|
|
1390
|
+
response2.headers.set(key, value);
|
|
1391
|
+
});
|
|
1115
1392
|
}
|
|
1116
1393
|
detector.emit("agent.blocked", result, context);
|
|
1117
1394
|
return response2;
|
|
@@ -1121,17 +1398,19 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1121
1398
|
case "rewrite":
|
|
1122
1399
|
return server.NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1123
1400
|
case "log":
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1401
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1402
|
+
console.debug("AgentShield: Agent detected", {
|
|
1403
|
+
ipAddress: context.ipAddress,
|
|
1404
|
+
userAgent: context.userAgent,
|
|
1405
|
+
confidence: result.confidence,
|
|
1406
|
+
reasons: result.reasons,
|
|
1407
|
+
pathname: request.nextUrl.pathname
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1131
1410
|
break;
|
|
1132
1411
|
case "allow":
|
|
1133
1412
|
default:
|
|
1134
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1413
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1135
1414
|
detector.emit("agent.allowed", result, context);
|
|
1136
1415
|
}
|
|
1137
1416
|
break;
|
|
@@ -1143,14 +1422,11 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1143
1422
|
};
|
|
1144
1423
|
let response = server.NextResponse.next();
|
|
1145
1424
|
response.headers.set("x-agentshield-detected", result.isAgent.toString());
|
|
1146
|
-
response.headers.set(
|
|
1147
|
-
"x-agentshield-confidence",
|
|
1148
|
-
result.confidence.toString()
|
|
1149
|
-
);
|
|
1425
|
+
response.headers.set("x-agentshield-confidence", result.confidence.toString());
|
|
1150
1426
|
if (result.detectedAgent?.name) {
|
|
1151
1427
|
response.headers.set("x-agentshield-agent", result.detectedAgent.name);
|
|
1152
1428
|
}
|
|
1153
|
-
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1429
|
+
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1154
1430
|
response = await sessionTracker.track(request, response, result);
|
|
1155
1431
|
response.headers.set("x-agentshield-session", "new");
|
|
1156
1432
|
detector.emit("agent.session.started", result, context);
|
|
@@ -1168,7 +1444,7 @@ var middlewareInstance = null;
|
|
|
1168
1444
|
var isInitializing = false;
|
|
1169
1445
|
var initPromise2 = null;
|
|
1170
1446
|
function createAgentShieldMiddleware2(config) {
|
|
1171
|
-
return async function
|
|
1447
|
+
return async function agentShieldMiddleware2(request) {
|
|
1172
1448
|
if (!middlewareInstance) {
|
|
1173
1449
|
if (!isInitializing) {
|
|
1174
1450
|
isInitializing = true;
|
|
@@ -1186,7 +1462,7 @@ function createAgentShieldMiddleware2(config) {
|
|
|
1186
1462
|
}
|
|
1187
1463
|
|
|
1188
1464
|
// src/edge-safe-detector.ts
|
|
1189
|
-
var
|
|
1465
|
+
var AI_AGENT_PATTERNS = [
|
|
1190
1466
|
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
1191
1467
|
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
1192
1468
|
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
@@ -1207,9 +1483,9 @@ var EdgeSafeDetector = class {
|
|
|
1207
1483
|
}
|
|
1208
1484
|
const userAgent = input.userAgent || normalizedHeaders["user-agent"] || "";
|
|
1209
1485
|
if (userAgent) {
|
|
1210
|
-
for (const { pattern, type, name } of
|
|
1486
|
+
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
1211
1487
|
if (pattern.test(userAgent)) {
|
|
1212
|
-
confidence =
|
|
1488
|
+
confidence = 85;
|
|
1213
1489
|
reasons.push(`known_pattern:${type}`);
|
|
1214
1490
|
detectedAgent = { type, name };
|
|
1215
1491
|
break;
|
|
@@ -1231,31 +1507,32 @@ var EdgeSafeDetector = class {
|
|
|
1231
1507
|
if (!hasAcceptLanguage) missingHeaders.push("accept-language");
|
|
1232
1508
|
if (!hasCookies) missingHeaders.push("cookies");
|
|
1233
1509
|
if (missingHeaders.length >= 2) {
|
|
1234
|
-
confidence = Math.max(confidence,
|
|
1510
|
+
confidence = Math.max(confidence, 85);
|
|
1235
1511
|
reasons.push("browser_ua_missing_headers");
|
|
1236
1512
|
if (!detectedAgent && hasChrome && !hasSecChUa) {
|
|
1237
1513
|
detectedAgent = { type: "perplexity", name: "Perplexity" };
|
|
1238
1514
|
}
|
|
1239
1515
|
}
|
|
1240
1516
|
}
|
|
1241
|
-
const aiHeaders = [
|
|
1242
|
-
"openai-conversation-id",
|
|
1243
|
-
"anthropic-client-id",
|
|
1244
|
-
"x-goog-api-client"
|
|
1245
|
-
];
|
|
1517
|
+
const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"];
|
|
1246
1518
|
const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]);
|
|
1247
1519
|
if (foundAiHeaders.length > 0) {
|
|
1248
|
-
confidence = Math.max(confidence,
|
|
1520
|
+
confidence = Math.max(confidence, 60);
|
|
1249
1521
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
1250
1522
|
}
|
|
1251
1523
|
return {
|
|
1252
|
-
isAgent: confidence >
|
|
1524
|
+
isAgent: confidence > 30,
|
|
1525
|
+
// Updated to 0-100 scale (was 0.3)
|
|
1253
1526
|
confidence,
|
|
1527
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1528
|
+
signals: [],
|
|
1529
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
1254
1530
|
detectedAgent,
|
|
1255
1531
|
reasons,
|
|
1256
1532
|
verificationMethod: "pattern",
|
|
1257
1533
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1258
|
-
confidenceLevel: confidence >=
|
|
1534
|
+
confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low"
|
|
1535
|
+
// Updated to 0-100 scale
|
|
1259
1536
|
};
|
|
1260
1537
|
}
|
|
1261
1538
|
};
|
|
@@ -1458,11 +1735,7 @@ var RedisStorageAdapter = class {
|
|
|
1458
1735
|
}
|
|
1459
1736
|
async cleanup(olderThan) {
|
|
1460
1737
|
const cutoff = olderThan.getTime();
|
|
1461
|
-
await this.redis.zremrangebyrank(
|
|
1462
|
-
this.timelineKey(),
|
|
1463
|
-
0,
|
|
1464
|
-
Math.floor(cutoff / 1e3)
|
|
1465
|
-
);
|
|
1738
|
+
await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3));
|
|
1466
1739
|
}
|
|
1467
1740
|
};
|
|
1468
1741
|
|
|
@@ -1483,7 +1756,10 @@ async function createStorageAdapter(config) {
|
|
|
1483
1756
|
});
|
|
1484
1757
|
return new RedisStorageAdapter(redis, config.ttl);
|
|
1485
1758
|
} catch (error) {
|
|
1486
|
-
console.warn(
|
|
1759
|
+
console.warn(
|
|
1760
|
+
"[AgentShield] Failed to initialize Redis storage, falling back to memory:",
|
|
1761
|
+
error
|
|
1762
|
+
);
|
|
1487
1763
|
return new MemoryStorageAdapter();
|
|
1488
1764
|
}
|
|
1489
1765
|
}
|
|
@@ -1545,7 +1821,9 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1545
1821
|
detectorInitPromise = (async () => {
|
|
1546
1822
|
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge";
|
|
1547
1823
|
if (isEdgeRuntime) {
|
|
1548
|
-
|
|
1824
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1825
|
+
console.debug("[AgentShield] Edge Runtime detected - using pattern detection");
|
|
1826
|
+
}
|
|
1549
1827
|
detector = new EdgeSafeDetector();
|
|
1550
1828
|
} else {
|
|
1551
1829
|
try {
|
|
@@ -1555,17 +1833,27 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1555
1833
|
const wasmAvailable = await wasmUtils.checkWasmAvailability();
|
|
1556
1834
|
if (wasmAvailable) {
|
|
1557
1835
|
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1558
|
-
|
|
1836
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1837
|
+
console.debug(
|
|
1838
|
+
"[AgentShield] \u2705 WASM support detected - enhanced detection enabled"
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1559
1841
|
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1560
1842
|
if (requestUrl && "setBaseUrl" in detector) {
|
|
1561
1843
|
detector.setBaseUrl(requestUrl);
|
|
1562
1844
|
}
|
|
1563
1845
|
} else {
|
|
1564
|
-
|
|
1846
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1847
|
+
console.debug(
|
|
1848
|
+
"[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)"
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1565
1851
|
detector = new EdgeSafeDetector();
|
|
1566
1852
|
}
|
|
1567
1853
|
} catch (wasmError) {
|
|
1568
|
-
|
|
1854
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1855
|
+
console.debug("[AgentShield] WASM utilities not available, using pattern detection");
|
|
1856
|
+
}
|
|
1569
1857
|
detector = new EdgeSafeDetector();
|
|
1570
1858
|
}
|
|
1571
1859
|
} catch (error) {
|
|
@@ -1610,7 +1898,10 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1610
1898
|
if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
|
|
1611
1899
|
if (sessionTrackingEnabled) {
|
|
1612
1900
|
const storage = await getStorage();
|
|
1613
|
-
const sessionId = sessionManager.generateSessionId(
|
|
1901
|
+
const sessionId = sessionManager.generateSessionId(
|
|
1902
|
+
ipAddress || void 0,
|
|
1903
|
+
userAgent || void 0
|
|
1904
|
+
);
|
|
1614
1905
|
const event = {
|
|
1615
1906
|
eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
1616
1907
|
sessionId,
|
|
@@ -1676,15 +1967,18 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1676
1967
|
{ status }
|
|
1677
1968
|
);
|
|
1678
1969
|
response2.headers.set("x-agentshield-detected", "true");
|
|
1679
|
-
response2.headers.set(
|
|
1970
|
+
response2.headers.set(
|
|
1971
|
+
"x-agentshield-confidence",
|
|
1972
|
+
String(Math.round(finalConfidence * 100))
|
|
1973
|
+
);
|
|
1680
1974
|
response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1681
1975
|
response2.headers.set("x-agentshield-verification", verificationMethod);
|
|
1682
1976
|
return response2;
|
|
1683
1977
|
}
|
|
1684
|
-
case "log":
|
|
1978
|
+
case "log": {
|
|
1685
1979
|
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
1686
|
-
if (isInteresting) {
|
|
1687
|
-
console.
|
|
1980
|
+
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
1981
|
+
console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
|
|
1688
1982
|
agent: result.detectedAgent?.name,
|
|
1689
1983
|
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
1690
1984
|
path: pathWithQuery,
|
|
@@ -1692,6 +1986,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1692
1986
|
});
|
|
1693
1987
|
}
|
|
1694
1988
|
break;
|
|
1989
|
+
}
|
|
1695
1990
|
}
|
|
1696
1991
|
}
|
|
1697
1992
|
const response = server.NextResponse.next();
|
|
@@ -1710,6 +2005,294 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1710
2005
|
};
|
|
1711
2006
|
}
|
|
1712
2007
|
|
|
2008
|
+
// src/api-client.ts
|
|
2009
|
+
var DEFAULT_BASE_URL = "https://api.agentshield.ai";
|
|
2010
|
+
var DEFAULT_TIMEOUT = 5e3;
|
|
2011
|
+
var AgentShieldClient = class {
|
|
2012
|
+
apiKey;
|
|
2013
|
+
baseUrl;
|
|
2014
|
+
timeout;
|
|
2015
|
+
debug;
|
|
2016
|
+
constructor(config) {
|
|
2017
|
+
if (!config.apiKey) {
|
|
2018
|
+
throw new Error("AgentShield API key is required");
|
|
2019
|
+
}
|
|
2020
|
+
this.apiKey = config.apiKey;
|
|
2021
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
2022
|
+
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
2023
|
+
this.debug = config.debug || false;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Call the enforce API to check if a request should be allowed
|
|
2027
|
+
*/
|
|
2028
|
+
async enforce(input) {
|
|
2029
|
+
const startTime = Date.now();
|
|
2030
|
+
try {
|
|
2031
|
+
const controller = new AbortController();
|
|
2032
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2033
|
+
try {
|
|
2034
|
+
const response = await fetch(`${this.baseUrl}/api/v1/enforce`, {
|
|
2035
|
+
method: "POST",
|
|
2036
|
+
headers: {
|
|
2037
|
+
"Content-Type": "application/json",
|
|
2038
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2039
|
+
"X-Request-ID": input.requestId || crypto.randomUUID()
|
|
2040
|
+
},
|
|
2041
|
+
body: JSON.stringify(input),
|
|
2042
|
+
signal: controller.signal
|
|
2043
|
+
});
|
|
2044
|
+
clearTimeout(timeoutId);
|
|
2045
|
+
const data = await response.json();
|
|
2046
|
+
if (this.debug) {
|
|
2047
|
+
console.log("[AgentShield] Enforce response:", {
|
|
2048
|
+
status: response.status,
|
|
2049
|
+
action: data.data?.decision.action,
|
|
2050
|
+
processingTimeMs: Date.now() - startTime
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
if (!response.ok) {
|
|
2054
|
+
return {
|
|
2055
|
+
success: false,
|
|
2056
|
+
error: {
|
|
2057
|
+
code: `HTTP_${response.status}`,
|
|
2058
|
+
message: data.error?.message || `HTTP error: ${response.status}`
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
return data;
|
|
2063
|
+
} catch (error) {
|
|
2064
|
+
clearTimeout(timeoutId);
|
|
2065
|
+
throw error;
|
|
2066
|
+
}
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2069
|
+
if (this.debug) {
|
|
2070
|
+
console.warn("[AgentShield] Request timed out");
|
|
2071
|
+
}
|
|
2072
|
+
return {
|
|
2073
|
+
success: false,
|
|
2074
|
+
error: {
|
|
2075
|
+
code: "TIMEOUT",
|
|
2076
|
+
message: `Request timed out after ${this.timeout}ms`
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
if (this.debug) {
|
|
2081
|
+
console.error("[AgentShield] Request failed:", error);
|
|
2082
|
+
}
|
|
2083
|
+
return {
|
|
2084
|
+
success: false,
|
|
2085
|
+
error: {
|
|
2086
|
+
code: "NETWORK_ERROR",
|
|
2087
|
+
message: error instanceof Error ? error.message : "Network request failed"
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Quick check - returns just the action without full response parsing
|
|
2094
|
+
* Useful for very fast middleware that just needs allow/block
|
|
2095
|
+
*/
|
|
2096
|
+
async quickCheck(input) {
|
|
2097
|
+
const result = await this.enforce(input);
|
|
2098
|
+
if (!result.success || !result.data) {
|
|
2099
|
+
return {
|
|
2100
|
+
action: "allow",
|
|
2101
|
+
error: result.error?.message
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
action: result.data.decision.action
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
var clientInstance = null;
|
|
2110
|
+
function getAgentShieldClient(config) {
|
|
2111
|
+
if (!clientInstance) {
|
|
2112
|
+
const apiKey = config?.apiKey || process.env.AGENTSHIELD_API_KEY;
|
|
2113
|
+
if (!apiKey) {
|
|
2114
|
+
throw new Error(
|
|
2115
|
+
"AgentShield API key is required. Set AGENTSHIELD_API_KEY environment variable or pass apiKey in config."
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
clientInstance = new AgentShieldClient({
|
|
2119
|
+
apiKey,
|
|
2120
|
+
baseUrl: config?.baseUrl || process.env.AGENTSHIELD_API_URL,
|
|
2121
|
+
timeout: config?.timeout,
|
|
2122
|
+
debug: config?.debug || process.env.AGENTSHIELD_DEBUG === "true"
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
return clientInstance;
|
|
2126
|
+
}
|
|
2127
|
+
function resetAgentShieldClient() {
|
|
2128
|
+
clientInstance = null;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/api-middleware.ts
|
|
2132
|
+
function matchPath(path, pattern) {
|
|
2133
|
+
if (pattern === path) return true;
|
|
2134
|
+
if (pattern.includes("*")) {
|
|
2135
|
+
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
2136
|
+
return new RegExp(`^${regexPattern}$`).test(path);
|
|
2137
|
+
}
|
|
2138
|
+
if (pattern.endsWith("/")) {
|
|
2139
|
+
return path.startsWith(pattern) || path === pattern.slice(0, -1);
|
|
2140
|
+
}
|
|
2141
|
+
return path.startsWith(pattern);
|
|
2142
|
+
}
|
|
2143
|
+
function shouldSkipPath(path, skipPaths) {
|
|
2144
|
+
return skipPaths.some((pattern) => matchPath(path, pattern));
|
|
2145
|
+
}
|
|
2146
|
+
function shouldIncludePath(path, includePaths) {
|
|
2147
|
+
if (!includePaths || includePaths.length === 0) return true;
|
|
2148
|
+
return includePaths.some((pattern) => matchPath(path, pattern));
|
|
2149
|
+
}
|
|
2150
|
+
function buildBlockedResponse(decision, config) {
|
|
2151
|
+
const status = config.blockedResponse?.status ?? 403;
|
|
2152
|
+
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2153
|
+
const response = server.NextResponse.json(
|
|
2154
|
+
{
|
|
2155
|
+
error: message,
|
|
2156
|
+
code: "AGENT_BLOCKED",
|
|
2157
|
+
reason: decision.reason,
|
|
2158
|
+
agentType: decision.agentType
|
|
2159
|
+
},
|
|
2160
|
+
{ status }
|
|
2161
|
+
);
|
|
2162
|
+
if (config.blockedResponse?.headers) {
|
|
2163
|
+
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2164
|
+
response.headers.set(key, value);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
response.headers.set("X-AgentShield-Action", decision.action);
|
|
2168
|
+
response.headers.set("X-AgentShield-Reason", decision.reason);
|
|
2169
|
+
return response;
|
|
2170
|
+
}
|
|
2171
|
+
function buildRedirectResponse(request, decision, config) {
|
|
2172
|
+
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2173
|
+
const url = new URL(redirectUrl, request.url);
|
|
2174
|
+
url.searchParams.set("reason", decision.reason);
|
|
2175
|
+
if (decision.agentType) {
|
|
2176
|
+
url.searchParams.set("agent", decision.agentType);
|
|
2177
|
+
}
|
|
2178
|
+
return server.NextResponse.redirect(url);
|
|
2179
|
+
}
|
|
2180
|
+
function withAgentShield(config = {}) {
|
|
2181
|
+
let client = null;
|
|
2182
|
+
const getClient = () => {
|
|
2183
|
+
if (!client) {
|
|
2184
|
+
client = getAgentShieldClient({
|
|
2185
|
+
apiKey: config.apiKey,
|
|
2186
|
+
baseUrl: config.apiUrl,
|
|
2187
|
+
timeout: config.timeout,
|
|
2188
|
+
debug: config.debug
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
return client;
|
|
2192
|
+
};
|
|
2193
|
+
const defaultSkipPaths = [
|
|
2194
|
+
"/_next/static/*",
|
|
2195
|
+
"/_next/image/*",
|
|
2196
|
+
"/favicon.ico",
|
|
2197
|
+
"/robots.txt",
|
|
2198
|
+
"/sitemap.xml"
|
|
2199
|
+
];
|
|
2200
|
+
const skipPaths = [...defaultSkipPaths, ...config.skipPaths || []];
|
|
2201
|
+
const failOpen = config.failOpen ?? true;
|
|
2202
|
+
return async function middleware(request) {
|
|
2203
|
+
const path = request.nextUrl.pathname;
|
|
2204
|
+
const startTime = Date.now();
|
|
2205
|
+
if (shouldSkipPath(path, skipPaths)) {
|
|
2206
|
+
return server.NextResponse.next();
|
|
2207
|
+
}
|
|
2208
|
+
if (!shouldIncludePath(path, config.includePaths)) {
|
|
2209
|
+
return server.NextResponse.next();
|
|
2210
|
+
}
|
|
2211
|
+
try {
|
|
2212
|
+
const result = await getClient().enforce({
|
|
2213
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
2214
|
+
userAgent: request.headers.get("user-agent") || void 0,
|
|
2215
|
+
ipAddress: request.ip || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || void 0,
|
|
2216
|
+
path,
|
|
2217
|
+
url: request.url,
|
|
2218
|
+
method: request.method,
|
|
2219
|
+
requestId: request.headers.get("x-request-id") || void 0,
|
|
2220
|
+
options: {
|
|
2221
|
+
includeDetectionResult: config.debug
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
if (!result.success || !result.data) {
|
|
2225
|
+
if (config.debug) {
|
|
2226
|
+
console.warn("[AgentShield] API error:", result.error);
|
|
2227
|
+
}
|
|
2228
|
+
if (failOpen) {
|
|
2229
|
+
return server.NextResponse.next();
|
|
2230
|
+
}
|
|
2231
|
+
return server.NextResponse.json(
|
|
2232
|
+
{ error: "Security check failed", code: "API_ERROR" },
|
|
2233
|
+
{ status: 503 }
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
const decision = result.data.decision;
|
|
2237
|
+
if (config.debug) {
|
|
2238
|
+
console.log("[AgentShield] Decision:", {
|
|
2239
|
+
path,
|
|
2240
|
+
action: decision.action,
|
|
2241
|
+
isAgent: decision.isAgent,
|
|
2242
|
+
confidence: decision.confidence,
|
|
2243
|
+
agentName: decision.agentName,
|
|
2244
|
+
processingTimeMs: Date.now() - startTime
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
if (decision.isAgent && config.onAgentDetected) {
|
|
2248
|
+
await config.onAgentDetected(request, decision);
|
|
2249
|
+
}
|
|
2250
|
+
switch (decision.action) {
|
|
2251
|
+
case "block": {
|
|
2252
|
+
if (config.customBlockedResponse) {
|
|
2253
|
+
return config.customBlockedResponse(request, decision);
|
|
2254
|
+
}
|
|
2255
|
+
if (config.onBlock === "redirect") {
|
|
2256
|
+
return buildRedirectResponse(request, decision, config);
|
|
2257
|
+
}
|
|
2258
|
+
return buildBlockedResponse(decision, config);
|
|
2259
|
+
}
|
|
2260
|
+
case "redirect": {
|
|
2261
|
+
return buildRedirectResponse(request, decision, config);
|
|
2262
|
+
}
|
|
2263
|
+
case "challenge": {
|
|
2264
|
+
return buildRedirectResponse(request, decision, config);
|
|
2265
|
+
}
|
|
2266
|
+
case "log":
|
|
2267
|
+
case "allow":
|
|
2268
|
+
default: {
|
|
2269
|
+
const response = server.NextResponse.next();
|
|
2270
|
+
if (decision.isAgent) {
|
|
2271
|
+
response.headers.set("X-AgentShield-Detected", "true");
|
|
2272
|
+
response.headers.set("X-AgentShield-Confidence", decision.confidence.toString());
|
|
2273
|
+
if (decision.agentName) {
|
|
2274
|
+
response.headers.set("X-AgentShield-Agent", decision.agentName);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return response;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
if (config.debug) {
|
|
2282
|
+
console.error("[AgentShield] Middleware error:", error);
|
|
2283
|
+
}
|
|
2284
|
+
if (failOpen) {
|
|
2285
|
+
return server.NextResponse.next();
|
|
2286
|
+
}
|
|
2287
|
+
return server.NextResponse.json(
|
|
2288
|
+
{ error: "Security check failed", code: "MIDDLEWARE_ERROR" },
|
|
2289
|
+
{ status: 503 }
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
var agentShieldMiddleware = withAgentShield();
|
|
2295
|
+
|
|
1713
2296
|
// src/index.ts
|
|
1714
2297
|
var VERSION = "0.1.0";
|
|
1715
2298
|
/**
|
|
@@ -1718,12 +2301,17 @@ var VERSION = "0.1.0";
|
|
|
1718
2301
|
* @license MIT OR Apache-2.0
|
|
1719
2302
|
*/
|
|
1720
2303
|
|
|
2304
|
+
exports.AgentShieldClient = AgentShieldClient;
|
|
1721
2305
|
exports.EdgeSessionTracker = EdgeSessionTracker;
|
|
1722
2306
|
exports.StatelessSessionChecker = StatelessSessionChecker;
|
|
1723
2307
|
exports.VERSION = VERSION;
|
|
2308
|
+
exports.agentShieldMiddleware = agentShieldMiddleware;
|
|
1724
2309
|
exports.createAgentShieldMiddleware = createAgentShieldMiddleware2;
|
|
1725
2310
|
exports.createAgentShieldMiddlewareBase = createAgentShieldMiddleware;
|
|
1726
2311
|
exports.createEnhancedAgentShieldMiddleware = createEnhancedAgentShieldMiddleware;
|
|
1727
2312
|
exports.createMiddleware = createAgentShieldMiddleware2;
|
|
2313
|
+
exports.getAgentShieldClient = getAgentShieldClient;
|
|
2314
|
+
exports.resetAgentShieldClient = resetAgentShieldClient;
|
|
2315
|
+
exports.withAgentShield = withAgentShield;
|
|
1728
2316
|
//# sourceMappingURL=index.js.map
|
|
1729
2317
|
//# sourceMappingURL=index.js.map
|