@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.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { loadRulesSync, mapVerificationMethod } from '@kya-os/agentshield-shared';
|
|
1
2
|
import { NextResponse } from 'next/server';
|
|
3
|
+
import * as ed25519 from '@noble/ed25519';
|
|
4
|
+
import { sha512 } from '@noble/hashes/sha2.js';
|
|
2
5
|
|
|
3
6
|
var __defProp = Object.defineProperty;
|
|
4
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -45,26 +48,31 @@ async function initWasm() {
|
|
|
45
48
|
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
46
49
|
}
|
|
47
50
|
const streamResponse = response2.clone();
|
|
48
|
-
const { instance } = await WebAssembly.instantiateStreaming(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
console.log("WASM:", ptr, len);
|
|
54
|
-
},
|
|
55
|
-
__wbindgen_throw: (ptr, len) => {
|
|
56
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
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);
|
|
57
56
|
}
|
|
57
|
+
},
|
|
58
|
+
__wbindgen_throw: (ptr, len) => {
|
|
59
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
|
-
);
|
|
62
|
+
});
|
|
61
63
|
wasmInstance = instance;
|
|
62
64
|
wasmExports = instance.exports;
|
|
63
|
-
|
|
65
|
+
if (process.env.NODE_ENV !== "production") {
|
|
66
|
+
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
67
|
+
}
|
|
64
68
|
return;
|
|
65
69
|
} catch (streamError) {
|
|
66
70
|
if (!controller.signal.aborted) {
|
|
67
|
-
|
|
71
|
+
if (process.env.NODE_ENV !== "production") {
|
|
72
|
+
console.debug(
|
|
73
|
+
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
68
76
|
} else {
|
|
69
77
|
throw streamError;
|
|
70
78
|
}
|
|
@@ -80,7 +88,9 @@ async function initWasm() {
|
|
|
80
88
|
const imports = {
|
|
81
89
|
wbg: {
|
|
82
90
|
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
83
|
-
|
|
91
|
+
if (process.env.NODE_ENV !== "production") {
|
|
92
|
+
console.debug("WASM:", ptr, len);
|
|
93
|
+
}
|
|
84
94
|
},
|
|
85
95
|
__wbindgen_throw: (ptr, len) => {
|
|
86
96
|
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
@@ -89,12 +99,20 @@ async function initWasm() {
|
|
|
89
99
|
};
|
|
90
100
|
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
91
101
|
wasmExports = wasmInstance.exports;
|
|
92
|
-
|
|
102
|
+
if (process.env.NODE_ENV !== "production") {
|
|
103
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
104
|
+
}
|
|
93
105
|
} catch (fetchError) {
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
);
|
|
96
111
|
} else {
|
|
97
|
-
console.warn(
|
|
112
|
+
console.warn(
|
|
113
|
+
"[AgentShield] Failed to fetch WASM file:",
|
|
114
|
+
error.message || "Unknown error"
|
|
115
|
+
);
|
|
98
116
|
}
|
|
99
117
|
wasmExports = null;
|
|
100
118
|
}
|
|
@@ -172,32 +190,20 @@ __export(edge_detector_with_wasm_exports, {
|
|
|
172
190
|
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
173
191
|
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
174
192
|
});
|
|
175
|
-
var
|
|
193
|
+
var rules2, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
176
194
|
var init_edge_detector_with_wasm = __esm({
|
|
177
195
|
"src/edge-detector-with-wasm.ts"() {
|
|
178
196
|
init_wasm_loader();
|
|
179
|
-
|
|
180
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
181
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
182
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
183
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
184
|
-
{ pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
|
|
185
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
186
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
187
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
188
|
-
];
|
|
189
|
-
CLOUD_PROVIDERS2 = {
|
|
190
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
191
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
192
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
193
|
-
};
|
|
197
|
+
rules2 = loadRulesSync();
|
|
194
198
|
EdgeAgentDetectorWithWasm = class {
|
|
195
199
|
constructor(enableWasm = true) {
|
|
196
200
|
this.enableWasm = enableWasm;
|
|
201
|
+
this.rules = rules2;
|
|
197
202
|
}
|
|
198
203
|
wasmEnabled = false;
|
|
199
204
|
initPromise = null;
|
|
200
205
|
baseUrl = null;
|
|
206
|
+
rules;
|
|
201
207
|
/**
|
|
202
208
|
* Set the base URL for WASM loading in Edge Runtime
|
|
203
209
|
*/
|
|
@@ -220,11 +226,14 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
220
226
|
this.initPromise = (async () => {
|
|
221
227
|
try {
|
|
222
228
|
const wasmAvailable = await isWasmAvailable();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
if (wasmAvailable) {
|
|
230
|
+
if (this.baseUrl) {
|
|
231
|
+
setWasmBaseUrl(this.baseUrl);
|
|
232
|
+
}
|
|
233
|
+
await initWasm();
|
|
234
|
+
this.wasmEnabled = true;
|
|
226
235
|
} else {
|
|
227
|
-
|
|
236
|
+
this.wasmEnabled = false;
|
|
228
237
|
}
|
|
229
238
|
} catch (error) {
|
|
230
239
|
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
@@ -249,69 +258,84 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
249
258
|
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
250
259
|
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
251
260
|
if (signatureAgent?.includes("chatgpt.com")) {
|
|
252
|
-
confidence =
|
|
261
|
+
confidence = 85;
|
|
253
262
|
reasons.push("signature_agent:chatgpt");
|
|
254
263
|
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
255
264
|
verificationMethod = "signature";
|
|
256
265
|
} else if (signaturePresent) {
|
|
257
|
-
confidence = Math.max(confidence,
|
|
266
|
+
confidence = Math.max(confidence, 40);
|
|
258
267
|
reasons.push("signature_present");
|
|
259
268
|
}
|
|
260
269
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
261
270
|
if (userAgent) {
|
|
262
|
-
for (const
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
reasons.push(`known_pattern:${type}`);
|
|
271
|
+
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
272
|
+
const matched = agentRule.patterns.some((pattern) => {
|
|
273
|
+
const regex = new RegExp(pattern, "i");
|
|
274
|
+
return regex.test(userAgent);
|
|
275
|
+
});
|
|
276
|
+
if (matched) {
|
|
277
|
+
const agentType = this.getAgentType(agentKey);
|
|
278
|
+
const agentName = this.getAgentName(agentKey);
|
|
279
|
+
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
280
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
273
281
|
if (!detectedAgent) {
|
|
274
|
-
detectedAgent = { type, name };
|
|
282
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
275
283
|
verificationMethod = "pattern";
|
|
276
284
|
}
|
|
277
285
|
break;
|
|
278
286
|
}
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"anthropic-client-id",
|
|
285
|
-
"x-goog-api-client",
|
|
286
|
-
"x-ms-copilot-id"
|
|
287
|
-
];
|
|
288
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
289
|
-
(header) => normalizedHeaders[header]
|
|
289
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
290
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
291
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
290
292
|
);
|
|
291
293
|
if (foundAiHeaders.length > 0) {
|
|
292
|
-
|
|
294
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
295
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
293
296
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
294
297
|
}
|
|
295
298
|
const ip = input.ip || input.ipAddress;
|
|
296
299
|
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
301
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
302
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
303
|
+
continue;
|
|
304
|
+
const matched = ipRule.ranges.some((range) => {
|
|
305
|
+
const prefix = range.split("/")[0];
|
|
306
|
+
const prefixParts = prefix.split(".");
|
|
307
|
+
const ipParts = ip.split(".");
|
|
308
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
309
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
});
|
|
315
|
+
if (matched) {
|
|
316
|
+
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
300
317
|
reasons.push(`cloud_provider:${provider}`);
|
|
301
318
|
break;
|
|
302
319
|
}
|
|
303
320
|
}
|
|
304
321
|
}
|
|
305
322
|
if (reasons.length > 2) {
|
|
306
|
-
confidence = Math.min(confidence * 1.2,
|
|
323
|
+
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
307
324
|
}
|
|
325
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
308
326
|
return {
|
|
309
|
-
isAgent: confidence >
|
|
327
|
+
isAgent: confidence > 30,
|
|
328
|
+
// 30% threshold
|
|
310
329
|
confidence,
|
|
330
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
331
|
+
signals: [],
|
|
332
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
311
333
|
...detectedAgent && { detectedAgent },
|
|
312
334
|
reasons,
|
|
313
|
-
...verificationMethod && {
|
|
314
|
-
|
|
335
|
+
...verificationMethod && {
|
|
336
|
+
verificationMethod: mapVerificationMethod(verificationMethod)
|
|
337
|
+
},
|
|
338
|
+
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
315
339
|
timestamp: /* @__PURE__ */ new Date()
|
|
316
340
|
};
|
|
317
341
|
}
|
|
@@ -328,15 +352,17 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
328
352
|
input.ip || input.ipAddress
|
|
329
353
|
);
|
|
330
354
|
if (wasmResult) {
|
|
331
|
-
console.log("[AgentShield] Using WASM detection result");
|
|
332
355
|
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
333
356
|
return {
|
|
334
357
|
isAgent: wasmResult.isAgent,
|
|
335
358
|
confidence: wasmResult.confidence,
|
|
359
|
+
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
360
|
+
signals: [],
|
|
361
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
336
362
|
...detectedAgent && { detectedAgent },
|
|
337
363
|
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
338
|
-
verificationMethod: wasmResult.verificationMethod,
|
|
339
|
-
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence >
|
|
364
|
+
verificationMethod: mapVerificationMethod(wasmResult.verificationMethod),
|
|
365
|
+
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
340
366
|
timestamp: /* @__PURE__ */ new Date()
|
|
341
367
|
};
|
|
342
368
|
}
|
|
@@ -345,15 +371,50 @@ var init_edge_detector_with_wasm = __esm({
|
|
|
345
371
|
}
|
|
346
372
|
}
|
|
347
373
|
const patternResult = await this.patternDetection(input);
|
|
348
|
-
if (this.wasmEnabled && patternResult.confidence >=
|
|
349
|
-
patternResult.confidence = Math.min(
|
|
374
|
+
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
375
|
+
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
350
376
|
patternResult.reasons.push("wasm_enhanced");
|
|
351
|
-
if (!patternResult.verificationMethod) {
|
|
352
|
-
patternResult.verificationMethod = "wasm-enhanced";
|
|
353
|
-
}
|
|
354
377
|
}
|
|
355
378
|
return patternResult;
|
|
356
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Get agent type from rule key
|
|
382
|
+
*/
|
|
383
|
+
getAgentType(agentKey) {
|
|
384
|
+
const typeMap = {
|
|
385
|
+
openai_gptbot: "openai",
|
|
386
|
+
anthropic_claude: "anthropic",
|
|
387
|
+
perplexity_bot: "perplexity",
|
|
388
|
+
google_ai: "google",
|
|
389
|
+
microsoft_ai: "microsoft",
|
|
390
|
+
meta_ai: "meta",
|
|
391
|
+
cohere_bot: "cohere",
|
|
392
|
+
huggingface_bot: "huggingface",
|
|
393
|
+
generic_bot: "generic",
|
|
394
|
+
dev_tools: "dev",
|
|
395
|
+
automation_tools: "automation"
|
|
396
|
+
};
|
|
397
|
+
return typeMap[agentKey] || agentKey;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get agent name from rule key
|
|
401
|
+
*/
|
|
402
|
+
getAgentName(agentKey) {
|
|
403
|
+
const nameMap = {
|
|
404
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
405
|
+
anthropic_claude: "Claude",
|
|
406
|
+
perplexity_bot: "Perplexity",
|
|
407
|
+
google_ai: "Google AI",
|
|
408
|
+
microsoft_ai: "Microsoft Copilot",
|
|
409
|
+
meta_ai: "Meta AI",
|
|
410
|
+
cohere_bot: "Cohere",
|
|
411
|
+
huggingface_bot: "HuggingFace",
|
|
412
|
+
generic_bot: "Generic Bot",
|
|
413
|
+
dev_tools: "Development Tool",
|
|
414
|
+
automation_tools: "Automation Tool"
|
|
415
|
+
};
|
|
416
|
+
return nameMap[agentKey] || agentKey;
|
|
417
|
+
}
|
|
357
418
|
/**
|
|
358
419
|
* Map agent names from WASM to consistent format
|
|
359
420
|
*/
|
|
@@ -432,19 +493,19 @@ async function checkWasmAvailability() {
|
|
|
432
493
|
}
|
|
433
494
|
}
|
|
434
495
|
function shouldIndicateWasmVerification(confidence) {
|
|
435
|
-
return confidence >=
|
|
496
|
+
return confidence >= 85 && confidence < 100;
|
|
436
497
|
}
|
|
437
498
|
function getWasmConfidenceBoost(baseConfidence, reasons = []) {
|
|
438
499
|
if (reasons.some(
|
|
439
500
|
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
440
501
|
)) {
|
|
441
|
-
return
|
|
502
|
+
return 100;
|
|
442
503
|
}
|
|
443
|
-
if (baseConfidence >=
|
|
444
|
-
return
|
|
504
|
+
if (baseConfidence >= 85) {
|
|
505
|
+
return 95;
|
|
445
506
|
}
|
|
446
|
-
if (baseConfidence >=
|
|
447
|
-
return Math.min(baseConfidence * 1.1,
|
|
507
|
+
if (baseConfidence >= 70) {
|
|
508
|
+
return Math.min(baseConfidence * 1.1, 90);
|
|
448
509
|
}
|
|
449
510
|
return baseConfidence;
|
|
450
511
|
}
|
|
@@ -463,19 +524,126 @@ var init_wasm_confidence = __esm({
|
|
|
463
524
|
"src/wasm-confidence.ts"() {
|
|
464
525
|
}
|
|
465
526
|
});
|
|
466
|
-
|
|
467
|
-
// src/signature-verifier.ts
|
|
527
|
+
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
468
528
|
var KNOWN_KEYS = {
|
|
469
529
|
chatgpt: [
|
|
470
530
|
{
|
|
471
531
|
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
472
532
|
// ChatGPT's current Ed25519 public key (base64)
|
|
533
|
+
// Source: https://chatgpt.com/.well-known/http-message-signatures-directory
|
|
473
534
|
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
474
|
-
validFrom:
|
|
475
|
-
|
|
535
|
+
validFrom: 1735689600,
|
|
536
|
+
// Jan 1, 2025 (from OpenAI's nbf)
|
|
537
|
+
// Extended expiration as fallback safety - API fetch should provide fresh keys
|
|
538
|
+
// Check OpenAI's well-known endpoint for actual expiration dates
|
|
539
|
+
validUntil: 1799625600
|
|
540
|
+
// Jan 1, 2027 (extended for fallback safety)
|
|
476
541
|
}
|
|
477
542
|
]
|
|
478
543
|
};
|
|
544
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
545
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
546
|
+
var CACHE_MAX_SIZE = 100;
|
|
547
|
+
function getApiBaseUrl() {
|
|
548
|
+
if (typeof window !== "undefined") {
|
|
549
|
+
return "/api/internal";
|
|
550
|
+
}
|
|
551
|
+
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);
|
|
552
|
+
if (baseUrl2) {
|
|
553
|
+
return baseUrl2.replace(/\/$/, "") + "/api/internal";
|
|
554
|
+
}
|
|
555
|
+
if (process.env.NODE_ENV !== "production") {
|
|
556
|
+
console.warn(
|
|
557
|
+
"[Signature] No base URL configured for server-side fetch. Using localhost fallback."
|
|
558
|
+
);
|
|
559
|
+
return "http://localhost:3000/api/internal";
|
|
560
|
+
}
|
|
561
|
+
console.error(
|
|
562
|
+
"[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
|
|
563
|
+
);
|
|
564
|
+
return "/api/internal";
|
|
565
|
+
}
|
|
566
|
+
function cleanupExpiredCache() {
|
|
567
|
+
const now = Date.now();
|
|
568
|
+
const entriesToDelete = [];
|
|
569
|
+
for (const [agent, cached] of keyCache.entries()) {
|
|
570
|
+
if (now - cached.cachedAt > CACHE_TTL_MS) {
|
|
571
|
+
entriesToDelete.push(agent);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
for (const agent of entriesToDelete) {
|
|
575
|
+
keyCache.delete(agent);
|
|
576
|
+
}
|
|
577
|
+
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
578
|
+
const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
|
|
579
|
+
agent,
|
|
580
|
+
cachedAt: cached.cachedAt
|
|
581
|
+
}));
|
|
582
|
+
entries.sort((a, b) => a.cachedAt - b.cachedAt);
|
|
583
|
+
const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
|
|
584
|
+
for (const entry of toRemove) {
|
|
585
|
+
keyCache.delete(entry.agent);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async function fetchKeysFromApi(agent) {
|
|
590
|
+
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
591
|
+
cleanupExpiredCache();
|
|
592
|
+
}
|
|
593
|
+
const cached = keyCache.get(agent);
|
|
594
|
+
if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
|
|
595
|
+
return cached.keys;
|
|
596
|
+
}
|
|
597
|
+
if (typeof fetch === "undefined") {
|
|
598
|
+
console.warn("[Signature] fetch() not available in this environment");
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
603
|
+
const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
|
|
604
|
+
const response = await fetch(url, {
|
|
605
|
+
method: "GET",
|
|
606
|
+
headers: {
|
|
607
|
+
"Content-Type": "application/json"
|
|
608
|
+
},
|
|
609
|
+
// 5 second timeout
|
|
610
|
+
signal: AbortSignal.timeout(5e3)
|
|
611
|
+
});
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const data = await response.json();
|
|
617
|
+
if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
|
|
618
|
+
console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
keyCache.set(agent, {
|
|
622
|
+
keys: data.keys,
|
|
623
|
+
cachedAt: Date.now()
|
|
624
|
+
});
|
|
625
|
+
return data.keys;
|
|
626
|
+
} catch (error) {
|
|
627
|
+
console.warn("[Signature] Error fetching keys from API, using fallback", {
|
|
628
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
629
|
+
agent
|
|
630
|
+
});
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function isValidAgent(agent) {
|
|
635
|
+
return agent in KNOWN_KEYS;
|
|
636
|
+
}
|
|
637
|
+
async function getKeysForAgent(agent) {
|
|
638
|
+
const apiKeys = await fetchKeysFromApi(agent);
|
|
639
|
+
if (apiKeys && apiKeys.length > 0) {
|
|
640
|
+
return apiKeys;
|
|
641
|
+
}
|
|
642
|
+
if (isValidAgent(agent)) {
|
|
643
|
+
return KNOWN_KEYS[agent];
|
|
644
|
+
}
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
479
647
|
function parseSignatureInput(signatureInput) {
|
|
480
648
|
try {
|
|
481
649
|
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
@@ -511,21 +679,29 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
|
511
679
|
case "@authority":
|
|
512
680
|
value = headers["host"] || headers["Host"] || "";
|
|
513
681
|
break;
|
|
514
|
-
default:
|
|
515
|
-
const key = Object.keys(headers).find(
|
|
516
|
-
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
517
|
-
);
|
|
682
|
+
default: {
|
|
683
|
+
const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
|
|
518
684
|
value = key ? headers[key] || "" : "";
|
|
519
685
|
break;
|
|
686
|
+
}
|
|
520
687
|
}
|
|
521
688
|
components.push(`"${headerName}": ${value}`);
|
|
522
689
|
}
|
|
523
690
|
return components.join("\n");
|
|
524
691
|
}
|
|
692
|
+
function base64ToBytes(base64) {
|
|
693
|
+
let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
694
|
+
const padding = standardBase64.length % 4;
|
|
695
|
+
if (padding) {
|
|
696
|
+
standardBase64 += "=".repeat(4 - padding);
|
|
697
|
+
}
|
|
698
|
+
const binaryString = atob(standardBase64);
|
|
699
|
+
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
700
|
+
}
|
|
525
701
|
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
526
702
|
try {
|
|
527
|
-
const publicKeyBytes =
|
|
528
|
-
const signatureBytes =
|
|
703
|
+
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
704
|
+
const signatureBytes = base64ToBytes(signatureBase64);
|
|
529
705
|
const messageBytes = new TextEncoder().encode(message);
|
|
530
706
|
if (publicKeyBytes.length !== 32) {
|
|
531
707
|
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
@@ -535,34 +711,36 @@ async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message)
|
|
|
535
711
|
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
536
712
|
return false;
|
|
537
713
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
714
|
+
return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);
|
|
715
|
+
} catch (nobleError) {
|
|
716
|
+
console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
|
|
717
|
+
try {
|
|
718
|
+
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
719
|
+
const signatureBytes = base64ToBytes(signatureBase64);
|
|
720
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
721
|
+
const publicKey = await crypto.subtle.importKey(
|
|
722
|
+
"raw",
|
|
723
|
+
publicKeyBytes.buffer,
|
|
724
|
+
{
|
|
725
|
+
name: "Ed25519",
|
|
726
|
+
namedCurve: "Ed25519"
|
|
727
|
+
},
|
|
728
|
+
false,
|
|
729
|
+
["verify"]
|
|
730
|
+
);
|
|
731
|
+
return await crypto.subtle.verify(
|
|
732
|
+
"Ed25519",
|
|
733
|
+
publicKey,
|
|
734
|
+
signatureBytes.buffer,
|
|
735
|
+
messageBytes
|
|
736
|
+
);
|
|
737
|
+
} catch (cryptoError) {
|
|
738
|
+
console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
|
|
739
|
+
nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
|
|
740
|
+
cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
|
|
741
|
+
});
|
|
742
|
+
return false;
|
|
564
743
|
}
|
|
565
|
-
return false;
|
|
566
744
|
}
|
|
567
745
|
}
|
|
568
746
|
async function verifyAgentSignature(method, path, headers) {
|
|
@@ -607,12 +785,12 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
607
785
|
}
|
|
608
786
|
}
|
|
609
787
|
let agent;
|
|
610
|
-
let
|
|
788
|
+
let agentKey;
|
|
611
789
|
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
612
790
|
agent = "ChatGPT";
|
|
613
|
-
|
|
791
|
+
agentKey = "chatgpt";
|
|
614
792
|
}
|
|
615
|
-
if (!agent || !
|
|
793
|
+
if (!agent || !agentKey) {
|
|
616
794
|
return {
|
|
617
795
|
isValid: false,
|
|
618
796
|
confidence: 0,
|
|
@@ -620,6 +798,15 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
620
798
|
verificationMethod: "none"
|
|
621
799
|
};
|
|
622
800
|
}
|
|
801
|
+
const knownKeys = await getKeysForAgent(agentKey);
|
|
802
|
+
if (knownKeys.length === 0) {
|
|
803
|
+
return {
|
|
804
|
+
isValid: false,
|
|
805
|
+
confidence: 0,
|
|
806
|
+
reason: "No keys available for agent",
|
|
807
|
+
verificationMethod: "none"
|
|
808
|
+
};
|
|
809
|
+
}
|
|
623
810
|
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
624
811
|
if (!key) {
|
|
625
812
|
return {
|
|
@@ -646,11 +833,7 @@ async function verifyAgentSignature(method, path, headers) {
|
|
|
646
833
|
if (signatureValue.endsWith(":")) {
|
|
647
834
|
signatureValue = signatureValue.slice(0, -1);
|
|
648
835
|
}
|
|
649
|
-
const isValid = await verifyEd25519Signature(
|
|
650
|
-
key.publicKey,
|
|
651
|
-
signatureValue,
|
|
652
|
-
signatureBase
|
|
653
|
-
);
|
|
836
|
+
const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
|
|
654
837
|
if (isValid) {
|
|
655
838
|
return {
|
|
656
839
|
isValid: true,
|
|
@@ -674,27 +857,27 @@ function hasSignatureHeaders(headers) {
|
|
|
674
857
|
}
|
|
675
858
|
function isChatGPTSignature(headers) {
|
|
676
859
|
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
677
|
-
|
|
860
|
+
if (!signatureAgent) {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
|
|
864
|
+
if (agentUrlStr === "https://chatgpt.com") {
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
const agentUrl = new URL(agentUrlStr);
|
|
869
|
+
const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
|
|
870
|
+
return allowedHosts.includes(agentUrl.host);
|
|
871
|
+
} catch {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
678
874
|
}
|
|
679
|
-
|
|
680
|
-
// src/edge-detector-wrapper.ts
|
|
681
|
-
var AI_AGENT_PATTERNS = [
|
|
682
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
683
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
684
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
685
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
686
|
-
{ pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
|
|
687
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
688
|
-
// Fallback
|
|
689
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
690
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
691
|
-
];
|
|
692
|
-
var CLOUD_PROVIDERS = {
|
|
693
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
694
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
695
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
696
|
-
};
|
|
875
|
+
var rules = loadRulesSync();
|
|
697
876
|
var EdgeAgentDetector = class {
|
|
877
|
+
rules;
|
|
878
|
+
constructor() {
|
|
879
|
+
this.rules = rules;
|
|
880
|
+
}
|
|
698
881
|
async analyze(input) {
|
|
699
882
|
const reasons = [];
|
|
700
883
|
let detectedAgent;
|
|
@@ -713,7 +896,7 @@ var EdgeAgentDetector = class {
|
|
|
713
896
|
headers
|
|
714
897
|
);
|
|
715
898
|
if (signatureResult.isValid) {
|
|
716
|
-
confidence = signatureResult.confidence;
|
|
899
|
+
confidence = signatureResult.confidence * 100;
|
|
717
900
|
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
718
901
|
if (signatureResult.agent) {
|
|
719
902
|
detectedAgent = {
|
|
@@ -726,7 +909,13 @@ var EdgeAgentDetector = class {
|
|
|
726
909
|
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
727
910
|
}
|
|
728
911
|
} else {
|
|
729
|
-
|
|
912
|
+
console.warn("[EdgeAgentDetector] Signature verification failed:", {
|
|
913
|
+
reason: signatureResult.reason,
|
|
914
|
+
agent: signatureResult.agent,
|
|
915
|
+
hasSignatureAgent: !!headers["signature-agent"] || !!headers["Signature-Agent"],
|
|
916
|
+
signatureAgentValue: headers["signature-agent"] || headers["Signature-Agent"]
|
|
917
|
+
});
|
|
918
|
+
confidence = Math.max(confidence, 30);
|
|
730
919
|
reasons.push("invalid_signature");
|
|
731
920
|
if (signatureResult.reason) {
|
|
732
921
|
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
@@ -738,68 +927,133 @@ var EdgeAgentDetector = class {
|
|
|
738
927
|
}
|
|
739
928
|
} catch (error) {
|
|
740
929
|
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
741
|
-
confidence = Math.max(confidence,
|
|
930
|
+
confidence = Math.max(confidence, 20);
|
|
742
931
|
reasons.push("signature_verification_error");
|
|
743
932
|
}
|
|
744
933
|
}
|
|
745
934
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
746
935
|
if (userAgent) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
936
|
+
const userAgentEntries = Object.entries(this.rules.rules.userAgents);
|
|
937
|
+
const genericKeys = ["generic_bot", "dev_tools", "automation_tools"];
|
|
938
|
+
const sortedEntries = userAgentEntries.sort((a, b) => {
|
|
939
|
+
const aIsGeneric = genericKeys.includes(a[0]);
|
|
940
|
+
const bIsGeneric = genericKeys.includes(b[0]);
|
|
941
|
+
if (aIsGeneric && !bIsGeneric) return 1;
|
|
942
|
+
if (!aIsGeneric && bIsGeneric) return -1;
|
|
943
|
+
return 0;
|
|
944
|
+
});
|
|
945
|
+
for (const [agentKey, agentRule] of sortedEntries) {
|
|
946
|
+
const rule = agentRule;
|
|
947
|
+
const matched = rule.patterns.some((pattern) => {
|
|
948
|
+
const regex = new RegExp(pattern, "i");
|
|
949
|
+
return regex.test(userAgent);
|
|
950
|
+
});
|
|
951
|
+
if (matched) {
|
|
952
|
+
const agentType = this.getAgentType(agentKey);
|
|
953
|
+
const agentName = this.getAgentName(agentKey);
|
|
954
|
+
confidence = Math.max(confidence, rule.confidence * 100);
|
|
955
|
+
reasons.push(`known_pattern:${agentType}`);
|
|
758
956
|
if (!detectedAgent) {
|
|
759
|
-
detectedAgent = { type, name };
|
|
957
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
760
958
|
verificationMethod = "pattern";
|
|
761
959
|
}
|
|
762
960
|
break;
|
|
763
961
|
}
|
|
764
962
|
}
|
|
765
963
|
}
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
"anthropic-client-id",
|
|
770
|
-
"x-goog-api-client",
|
|
771
|
-
"x-ms-copilot-id"
|
|
772
|
-
];
|
|
773
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
774
|
-
(header) => normalizedHeaders[header]
|
|
964
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
965
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
966
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
775
967
|
);
|
|
776
968
|
if (foundAiHeaders.length > 0) {
|
|
777
|
-
|
|
969
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence * 100));
|
|
970
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
778
971
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
779
972
|
}
|
|
780
973
|
const ip = input.ip || input.ipAddress;
|
|
781
974
|
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
975
|
+
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
976
|
+
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
977
|
+
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
978
|
+
continue;
|
|
979
|
+
const matched = ipRule.ranges.some((range) => {
|
|
980
|
+
const prefix = range.split("/")[0];
|
|
981
|
+
const prefixParts = prefix.split(".");
|
|
982
|
+
const ipParts = ip.split(".");
|
|
983
|
+
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
984
|
+
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return true;
|
|
989
|
+
});
|
|
990
|
+
if (matched) {
|
|
991
|
+
const rule = ipRule;
|
|
992
|
+
confidence = Math.max(confidence, rule.confidence * 40);
|
|
785
993
|
reasons.push(`cloud_provider:${provider}`);
|
|
786
994
|
break;
|
|
787
995
|
}
|
|
788
996
|
}
|
|
789
997
|
}
|
|
790
|
-
if (reasons.length > 2 && confidence <
|
|
791
|
-
confidence = Math.min(confidence * 1.2,
|
|
998
|
+
if (reasons.length > 2 && confidence < 100) {
|
|
999
|
+
confidence = Math.min(confidence * 1.2, 95);
|
|
792
1000
|
}
|
|
1001
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
793
1002
|
return {
|
|
794
|
-
isAgent: confidence >
|
|
1003
|
+
isAgent: confidence > 30,
|
|
1004
|
+
// Updated to 0-100 scale (was 0.3)
|
|
795
1005
|
confidence,
|
|
1006
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1007
|
+
signals: [],
|
|
1008
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
796
1009
|
...detectedAgent && { detectedAgent },
|
|
797
1010
|
reasons,
|
|
798
|
-
...verificationMethod && {
|
|
799
|
-
|
|
1011
|
+
...verificationMethod && {
|
|
1012
|
+
verificationMethod
|
|
1013
|
+
},
|
|
1014
|
+
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 80 ? "medium" : "high",
|
|
1015
|
+
// Updated to 0-100 scale
|
|
800
1016
|
timestamp: /* @__PURE__ */ new Date()
|
|
801
1017
|
};
|
|
802
1018
|
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Get agent type from rule key
|
|
1021
|
+
*/
|
|
1022
|
+
getAgentType(agentKey) {
|
|
1023
|
+
const typeMap = {
|
|
1024
|
+
openai_gptbot: "openai",
|
|
1025
|
+
anthropic_claude: "anthropic",
|
|
1026
|
+
perplexity_bot: "perplexity",
|
|
1027
|
+
google_ai: "google",
|
|
1028
|
+
microsoft_ai: "microsoft",
|
|
1029
|
+
meta_ai: "meta",
|
|
1030
|
+
cohere_bot: "cohere",
|
|
1031
|
+
huggingface_bot: "huggingface",
|
|
1032
|
+
generic_bot: "generic",
|
|
1033
|
+
dev_tools: "dev",
|
|
1034
|
+
automation_tools: "automation"
|
|
1035
|
+
};
|
|
1036
|
+
return typeMap[agentKey] || agentKey;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Get agent name from rule key
|
|
1040
|
+
*/
|
|
1041
|
+
getAgentName(agentKey) {
|
|
1042
|
+
const nameMap = {
|
|
1043
|
+
openai_gptbot: "ChatGPT/GPTBot",
|
|
1044
|
+
anthropic_claude: "Claude",
|
|
1045
|
+
perplexity_bot: "Perplexity",
|
|
1046
|
+
google_ai: "Google AI",
|
|
1047
|
+
microsoft_ai: "Microsoft Copilot",
|
|
1048
|
+
meta_ai: "Meta AI",
|
|
1049
|
+
cohere_bot: "Cohere",
|
|
1050
|
+
huggingface_bot: "HuggingFace",
|
|
1051
|
+
generic_bot: "Generic Bot",
|
|
1052
|
+
dev_tools: "Development Tool",
|
|
1053
|
+
automation_tools: "Automation Tool"
|
|
1054
|
+
};
|
|
1055
|
+
return nameMap[agentKey] || agentKey;
|
|
1056
|
+
}
|
|
803
1057
|
};
|
|
804
1058
|
var EdgeAgentDetectorWrapper = class {
|
|
805
1059
|
detector;
|
|
@@ -1012,7 +1266,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1012
1266
|
}) : null;
|
|
1013
1267
|
if (config.events) {
|
|
1014
1268
|
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1015
|
-
|
|
1269
|
+
if (handler) {
|
|
1270
|
+
detector.on(event, handler);
|
|
1271
|
+
}
|
|
1016
1272
|
});
|
|
1017
1273
|
}
|
|
1018
1274
|
const {
|
|
@@ -1044,19 +1300,22 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1044
1300
|
const response2 = NextResponse.next();
|
|
1045
1301
|
response2.headers.set("x-agentshield-detected", "true");
|
|
1046
1302
|
response2.headers.set("x-agentshield-agent", existingSession.agent);
|
|
1047
|
-
response2.headers.set(
|
|
1048
|
-
"x-agentshield-confidence",
|
|
1049
|
-
existingSession.confidence.toString()
|
|
1050
|
-
);
|
|
1303
|
+
response2.headers.set("x-agentshield-confidence", existingSession.confidence.toString());
|
|
1051
1304
|
response2.headers.set("x-agentshield-session", "continued");
|
|
1052
1305
|
response2.headers.set("x-agentshield-session-id", existingSession.id);
|
|
1053
1306
|
request.agentShield = {
|
|
1054
1307
|
result: {
|
|
1055
1308
|
isAgent: true,
|
|
1056
1309
|
confidence: existingSession.confidence,
|
|
1057
|
-
|
|
1310
|
+
detectionClass: { type: "AiAgent" },
|
|
1311
|
+
detectedAgent: {
|
|
1312
|
+
type: "ai_agent",
|
|
1313
|
+
name: existingSession.agent
|
|
1314
|
+
},
|
|
1058
1315
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1059
|
-
verificationMethod: "
|
|
1316
|
+
verificationMethod: "behavioral",
|
|
1317
|
+
reasons: ["Session continued"],
|
|
1318
|
+
signals: []
|
|
1060
1319
|
},
|
|
1061
1320
|
session: existingSession,
|
|
1062
1321
|
skipped: false
|
|
@@ -1086,7 +1345,7 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1086
1345
|
timestamp: /* @__PURE__ */ new Date()
|
|
1087
1346
|
};
|
|
1088
1347
|
const result = await detector.analyze(context);
|
|
1089
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1348
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1090
1349
|
if (onDetection) {
|
|
1091
1350
|
const customResponse = await onDetection(request, result);
|
|
1092
1351
|
if (customResponse) {
|
|
@@ -1105,11 +1364,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1105
1364
|
{ status: blockedResponse.status }
|
|
1106
1365
|
);
|
|
1107
1366
|
if (blockedResponse.headers) {
|
|
1108
|
-
Object.entries(blockedResponse.headers).forEach(
|
|
1109
|
-
(
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1112
|
-
);
|
|
1367
|
+
Object.entries(blockedResponse.headers).forEach(([key, value]) => {
|
|
1368
|
+
response2.headers.set(key, value);
|
|
1369
|
+
});
|
|
1113
1370
|
}
|
|
1114
1371
|
detector.emit("agent.blocked", result, context);
|
|
1115
1372
|
return response2;
|
|
@@ -1119,17 +1376,19 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1119
1376
|
case "rewrite":
|
|
1120
1377
|
return NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1121
1378
|
case "log":
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1379
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1380
|
+
console.debug("AgentShield: Agent detected", {
|
|
1381
|
+
ipAddress: context.ipAddress,
|
|
1382
|
+
userAgent: context.userAgent,
|
|
1383
|
+
confidence: result.confidence,
|
|
1384
|
+
reasons: result.reasons,
|
|
1385
|
+
pathname: request.nextUrl.pathname
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1129
1388
|
break;
|
|
1130
1389
|
case "allow":
|
|
1131
1390
|
default:
|
|
1132
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1391
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1133
1392
|
detector.emit("agent.allowed", result, context);
|
|
1134
1393
|
}
|
|
1135
1394
|
break;
|
|
@@ -1141,14 +1400,11 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1141
1400
|
};
|
|
1142
1401
|
let response = NextResponse.next();
|
|
1143
1402
|
response.headers.set("x-agentshield-detected", result.isAgent.toString());
|
|
1144
|
-
response.headers.set(
|
|
1145
|
-
"x-agentshield-confidence",
|
|
1146
|
-
result.confidence.toString()
|
|
1147
|
-
);
|
|
1403
|
+
response.headers.set("x-agentshield-confidence", result.confidence.toString());
|
|
1148
1404
|
if (result.detectedAgent?.name) {
|
|
1149
1405
|
response.headers.set("x-agentshield-agent", result.detectedAgent.name);
|
|
1150
1406
|
}
|
|
1151
|
-
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1407
|
+
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1152
1408
|
response = await sessionTracker.track(request, response, result);
|
|
1153
1409
|
response.headers.set("x-agentshield-session", "new");
|
|
1154
1410
|
detector.emit("agent.session.started", result, context);
|
|
@@ -1166,7 +1422,7 @@ var middlewareInstance = null;
|
|
|
1166
1422
|
var isInitializing = false;
|
|
1167
1423
|
var initPromise2 = null;
|
|
1168
1424
|
function createAgentShieldMiddleware2(config) {
|
|
1169
|
-
return async function
|
|
1425
|
+
return async function agentShieldMiddleware2(request) {
|
|
1170
1426
|
if (!middlewareInstance) {
|
|
1171
1427
|
if (!isInitializing) {
|
|
1172
1428
|
isInitializing = true;
|
|
@@ -1184,7 +1440,7 @@ function createAgentShieldMiddleware2(config) {
|
|
|
1184
1440
|
}
|
|
1185
1441
|
|
|
1186
1442
|
// src/edge-safe-detector.ts
|
|
1187
|
-
var
|
|
1443
|
+
var AI_AGENT_PATTERNS = [
|
|
1188
1444
|
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
1189
1445
|
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
1190
1446
|
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
@@ -1205,9 +1461,9 @@ var EdgeSafeDetector = class {
|
|
|
1205
1461
|
}
|
|
1206
1462
|
const userAgent = input.userAgent || normalizedHeaders["user-agent"] || "";
|
|
1207
1463
|
if (userAgent) {
|
|
1208
|
-
for (const { pattern, type, name } of
|
|
1464
|
+
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
1209
1465
|
if (pattern.test(userAgent)) {
|
|
1210
|
-
confidence =
|
|
1466
|
+
confidence = 85;
|
|
1211
1467
|
reasons.push(`known_pattern:${type}`);
|
|
1212
1468
|
detectedAgent = { type, name };
|
|
1213
1469
|
break;
|
|
@@ -1229,31 +1485,32 @@ var EdgeSafeDetector = class {
|
|
|
1229
1485
|
if (!hasAcceptLanguage) missingHeaders.push("accept-language");
|
|
1230
1486
|
if (!hasCookies) missingHeaders.push("cookies");
|
|
1231
1487
|
if (missingHeaders.length >= 2) {
|
|
1232
|
-
confidence = Math.max(confidence,
|
|
1488
|
+
confidence = Math.max(confidence, 85);
|
|
1233
1489
|
reasons.push("browser_ua_missing_headers");
|
|
1234
1490
|
if (!detectedAgent && hasChrome && !hasSecChUa) {
|
|
1235
1491
|
detectedAgent = { type: "perplexity", name: "Perplexity" };
|
|
1236
1492
|
}
|
|
1237
1493
|
}
|
|
1238
1494
|
}
|
|
1239
|
-
const aiHeaders = [
|
|
1240
|
-
"openai-conversation-id",
|
|
1241
|
-
"anthropic-client-id",
|
|
1242
|
-
"x-goog-api-client"
|
|
1243
|
-
];
|
|
1495
|
+
const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"];
|
|
1244
1496
|
const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]);
|
|
1245
1497
|
if (foundAiHeaders.length > 0) {
|
|
1246
|
-
confidence = Math.max(confidence,
|
|
1498
|
+
confidence = Math.max(confidence, 60);
|
|
1247
1499
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
1248
1500
|
}
|
|
1249
1501
|
return {
|
|
1250
|
-
isAgent: confidence >
|
|
1502
|
+
isAgent: confidence > 30,
|
|
1503
|
+
// Updated to 0-100 scale (was 0.3)
|
|
1251
1504
|
confidence,
|
|
1505
|
+
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1506
|
+
signals: [],
|
|
1507
|
+
// Will be populated by enhanced detection engine in future tasks
|
|
1252
1508
|
detectedAgent,
|
|
1253
1509
|
reasons,
|
|
1254
1510
|
verificationMethod: "pattern",
|
|
1255
1511
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1256
|
-
confidenceLevel: confidence >=
|
|
1512
|
+
confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low"
|
|
1513
|
+
// Updated to 0-100 scale
|
|
1257
1514
|
};
|
|
1258
1515
|
}
|
|
1259
1516
|
};
|
|
@@ -1456,11 +1713,7 @@ var RedisStorageAdapter = class {
|
|
|
1456
1713
|
}
|
|
1457
1714
|
async cleanup(olderThan) {
|
|
1458
1715
|
const cutoff = olderThan.getTime();
|
|
1459
|
-
await this.redis.zremrangebyrank(
|
|
1460
|
-
this.timelineKey(),
|
|
1461
|
-
0,
|
|
1462
|
-
Math.floor(cutoff / 1e3)
|
|
1463
|
-
);
|
|
1716
|
+
await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3));
|
|
1464
1717
|
}
|
|
1465
1718
|
};
|
|
1466
1719
|
|
|
@@ -1481,7 +1734,10 @@ async function createStorageAdapter(config) {
|
|
|
1481
1734
|
});
|
|
1482
1735
|
return new RedisStorageAdapter(redis, config.ttl);
|
|
1483
1736
|
} catch (error) {
|
|
1484
|
-
console.warn(
|
|
1737
|
+
console.warn(
|
|
1738
|
+
"[AgentShield] Failed to initialize Redis storage, falling back to memory:",
|
|
1739
|
+
error
|
|
1740
|
+
);
|
|
1485
1741
|
return new MemoryStorageAdapter();
|
|
1486
1742
|
}
|
|
1487
1743
|
}
|
|
@@ -1543,7 +1799,9 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1543
1799
|
detectorInitPromise = (async () => {
|
|
1544
1800
|
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge";
|
|
1545
1801
|
if (isEdgeRuntime) {
|
|
1546
|
-
|
|
1802
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1803
|
+
console.debug("[AgentShield] Edge Runtime detected - using pattern detection");
|
|
1804
|
+
}
|
|
1547
1805
|
detector = new EdgeSafeDetector();
|
|
1548
1806
|
} else {
|
|
1549
1807
|
try {
|
|
@@ -1553,17 +1811,27 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1553
1811
|
const wasmAvailable = await wasmUtils.checkWasmAvailability();
|
|
1554
1812
|
if (wasmAvailable) {
|
|
1555
1813
|
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1556
|
-
|
|
1814
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1815
|
+
console.debug(
|
|
1816
|
+
"[AgentShield] \u2705 WASM support detected - enhanced detection enabled"
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1557
1819
|
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1558
1820
|
if (requestUrl && "setBaseUrl" in detector) {
|
|
1559
1821
|
detector.setBaseUrl(requestUrl);
|
|
1560
1822
|
}
|
|
1561
1823
|
} else {
|
|
1562
|
-
|
|
1824
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1825
|
+
console.debug(
|
|
1826
|
+
"[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)"
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1563
1829
|
detector = new EdgeSafeDetector();
|
|
1564
1830
|
}
|
|
1565
1831
|
} catch (wasmError) {
|
|
1566
|
-
|
|
1832
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1833
|
+
console.debug("[AgentShield] WASM utilities not available, using pattern detection");
|
|
1834
|
+
}
|
|
1567
1835
|
detector = new EdgeSafeDetector();
|
|
1568
1836
|
}
|
|
1569
1837
|
} catch (error) {
|
|
@@ -1608,7 +1876,10 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1608
1876
|
if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
|
|
1609
1877
|
if (sessionTrackingEnabled) {
|
|
1610
1878
|
const storage = await getStorage();
|
|
1611
|
-
const sessionId = sessionManager.generateSessionId(
|
|
1879
|
+
const sessionId = sessionManager.generateSessionId(
|
|
1880
|
+
ipAddress || void 0,
|
|
1881
|
+
userAgent || void 0
|
|
1882
|
+
);
|
|
1612
1883
|
const event = {
|
|
1613
1884
|
eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
1614
1885
|
sessionId,
|
|
@@ -1674,15 +1945,18 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1674
1945
|
{ status }
|
|
1675
1946
|
);
|
|
1676
1947
|
response2.headers.set("x-agentshield-detected", "true");
|
|
1677
|
-
response2.headers.set(
|
|
1948
|
+
response2.headers.set(
|
|
1949
|
+
"x-agentshield-confidence",
|
|
1950
|
+
String(Math.round(finalConfidence * 100))
|
|
1951
|
+
);
|
|
1678
1952
|
response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1679
1953
|
response2.headers.set("x-agentshield-verification", verificationMethod);
|
|
1680
1954
|
return response2;
|
|
1681
1955
|
}
|
|
1682
|
-
case "log":
|
|
1956
|
+
case "log": {
|
|
1683
1957
|
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
1684
|
-
if (isInteresting) {
|
|
1685
|
-
console.
|
|
1958
|
+
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
1959
|
+
console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
|
|
1686
1960
|
agent: result.detectedAgent?.name,
|
|
1687
1961
|
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
1688
1962
|
path: pathWithQuery,
|
|
@@ -1690,6 +1964,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1690
1964
|
});
|
|
1691
1965
|
}
|
|
1692
1966
|
break;
|
|
1967
|
+
}
|
|
1693
1968
|
}
|
|
1694
1969
|
}
|
|
1695
1970
|
const response = NextResponse.next();
|
|
@@ -1708,6 +1983,294 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1708
1983
|
};
|
|
1709
1984
|
}
|
|
1710
1985
|
|
|
1986
|
+
// src/api-client.ts
|
|
1987
|
+
var DEFAULT_BASE_URL = "https://api.agentshield.ai";
|
|
1988
|
+
var DEFAULT_TIMEOUT = 5e3;
|
|
1989
|
+
var AgentShieldClient = class {
|
|
1990
|
+
apiKey;
|
|
1991
|
+
baseUrl;
|
|
1992
|
+
timeout;
|
|
1993
|
+
debug;
|
|
1994
|
+
constructor(config) {
|
|
1995
|
+
if (!config.apiKey) {
|
|
1996
|
+
throw new Error("AgentShield API key is required");
|
|
1997
|
+
}
|
|
1998
|
+
this.apiKey = config.apiKey;
|
|
1999
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
2000
|
+
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
2001
|
+
this.debug = config.debug || false;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Call the enforce API to check if a request should be allowed
|
|
2005
|
+
*/
|
|
2006
|
+
async enforce(input) {
|
|
2007
|
+
const startTime = Date.now();
|
|
2008
|
+
try {
|
|
2009
|
+
const controller = new AbortController();
|
|
2010
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2011
|
+
try {
|
|
2012
|
+
const response = await fetch(`${this.baseUrl}/api/v1/enforce`, {
|
|
2013
|
+
method: "POST",
|
|
2014
|
+
headers: {
|
|
2015
|
+
"Content-Type": "application/json",
|
|
2016
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2017
|
+
"X-Request-ID": input.requestId || crypto.randomUUID()
|
|
2018
|
+
},
|
|
2019
|
+
body: JSON.stringify(input),
|
|
2020
|
+
signal: controller.signal
|
|
2021
|
+
});
|
|
2022
|
+
clearTimeout(timeoutId);
|
|
2023
|
+
const data = await response.json();
|
|
2024
|
+
if (this.debug) {
|
|
2025
|
+
console.log("[AgentShield] Enforce response:", {
|
|
2026
|
+
status: response.status,
|
|
2027
|
+
action: data.data?.decision.action,
|
|
2028
|
+
processingTimeMs: Date.now() - startTime
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
if (!response.ok) {
|
|
2032
|
+
return {
|
|
2033
|
+
success: false,
|
|
2034
|
+
error: {
|
|
2035
|
+
code: `HTTP_${response.status}`,
|
|
2036
|
+
message: data.error?.message || `HTTP error: ${response.status}`
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
return data;
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
clearTimeout(timeoutId);
|
|
2043
|
+
throw error;
|
|
2044
|
+
}
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2047
|
+
if (this.debug) {
|
|
2048
|
+
console.warn("[AgentShield] Request timed out");
|
|
2049
|
+
}
|
|
2050
|
+
return {
|
|
2051
|
+
success: false,
|
|
2052
|
+
error: {
|
|
2053
|
+
code: "TIMEOUT",
|
|
2054
|
+
message: `Request timed out after ${this.timeout}ms`
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
if (this.debug) {
|
|
2059
|
+
console.error("[AgentShield] Request failed:", error);
|
|
2060
|
+
}
|
|
2061
|
+
return {
|
|
2062
|
+
success: false,
|
|
2063
|
+
error: {
|
|
2064
|
+
code: "NETWORK_ERROR",
|
|
2065
|
+
message: error instanceof Error ? error.message : "Network request failed"
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Quick check - returns just the action without full response parsing
|
|
2072
|
+
* Useful for very fast middleware that just needs allow/block
|
|
2073
|
+
*/
|
|
2074
|
+
async quickCheck(input) {
|
|
2075
|
+
const result = await this.enforce(input);
|
|
2076
|
+
if (!result.success || !result.data) {
|
|
2077
|
+
return {
|
|
2078
|
+
action: "allow",
|
|
2079
|
+
error: result.error?.message
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
return {
|
|
2083
|
+
action: result.data.decision.action
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
var clientInstance = null;
|
|
2088
|
+
function getAgentShieldClient(config) {
|
|
2089
|
+
if (!clientInstance) {
|
|
2090
|
+
const apiKey = config?.apiKey || process.env.AGENTSHIELD_API_KEY;
|
|
2091
|
+
if (!apiKey) {
|
|
2092
|
+
throw new Error(
|
|
2093
|
+
"AgentShield API key is required. Set AGENTSHIELD_API_KEY environment variable or pass apiKey in config."
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
clientInstance = new AgentShieldClient({
|
|
2097
|
+
apiKey,
|
|
2098
|
+
baseUrl: config?.baseUrl || process.env.AGENTSHIELD_API_URL,
|
|
2099
|
+
timeout: config?.timeout,
|
|
2100
|
+
debug: config?.debug || process.env.AGENTSHIELD_DEBUG === "true"
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
return clientInstance;
|
|
2104
|
+
}
|
|
2105
|
+
function resetAgentShieldClient() {
|
|
2106
|
+
clientInstance = null;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/api-middleware.ts
|
|
2110
|
+
function matchPath(path, pattern) {
|
|
2111
|
+
if (pattern === path) return true;
|
|
2112
|
+
if (pattern.includes("*")) {
|
|
2113
|
+
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
2114
|
+
return new RegExp(`^${regexPattern}$`).test(path);
|
|
2115
|
+
}
|
|
2116
|
+
if (pattern.endsWith("/")) {
|
|
2117
|
+
return path.startsWith(pattern) || path === pattern.slice(0, -1);
|
|
2118
|
+
}
|
|
2119
|
+
return path.startsWith(pattern);
|
|
2120
|
+
}
|
|
2121
|
+
function shouldSkipPath(path, skipPaths) {
|
|
2122
|
+
return skipPaths.some((pattern) => matchPath(path, pattern));
|
|
2123
|
+
}
|
|
2124
|
+
function shouldIncludePath(path, includePaths) {
|
|
2125
|
+
if (!includePaths || includePaths.length === 0) return true;
|
|
2126
|
+
return includePaths.some((pattern) => matchPath(path, pattern));
|
|
2127
|
+
}
|
|
2128
|
+
function buildBlockedResponse(decision, config) {
|
|
2129
|
+
const status = config.blockedResponse?.status ?? 403;
|
|
2130
|
+
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2131
|
+
const response = NextResponse.json(
|
|
2132
|
+
{
|
|
2133
|
+
error: message,
|
|
2134
|
+
code: "AGENT_BLOCKED",
|
|
2135
|
+
reason: decision.reason,
|
|
2136
|
+
agentType: decision.agentType
|
|
2137
|
+
},
|
|
2138
|
+
{ status }
|
|
2139
|
+
);
|
|
2140
|
+
if (config.blockedResponse?.headers) {
|
|
2141
|
+
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2142
|
+
response.headers.set(key, value);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
response.headers.set("X-AgentShield-Action", decision.action);
|
|
2146
|
+
response.headers.set("X-AgentShield-Reason", decision.reason);
|
|
2147
|
+
return response;
|
|
2148
|
+
}
|
|
2149
|
+
function buildRedirectResponse(request, decision, config) {
|
|
2150
|
+
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2151
|
+
const url = new URL(redirectUrl, request.url);
|
|
2152
|
+
url.searchParams.set("reason", decision.reason);
|
|
2153
|
+
if (decision.agentType) {
|
|
2154
|
+
url.searchParams.set("agent", decision.agentType);
|
|
2155
|
+
}
|
|
2156
|
+
return NextResponse.redirect(url);
|
|
2157
|
+
}
|
|
2158
|
+
function withAgentShield(config = {}) {
|
|
2159
|
+
let client = null;
|
|
2160
|
+
const getClient = () => {
|
|
2161
|
+
if (!client) {
|
|
2162
|
+
client = getAgentShieldClient({
|
|
2163
|
+
apiKey: config.apiKey,
|
|
2164
|
+
baseUrl: config.apiUrl,
|
|
2165
|
+
timeout: config.timeout,
|
|
2166
|
+
debug: config.debug
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
return client;
|
|
2170
|
+
};
|
|
2171
|
+
const defaultSkipPaths = [
|
|
2172
|
+
"/_next/static/*",
|
|
2173
|
+
"/_next/image/*",
|
|
2174
|
+
"/favicon.ico",
|
|
2175
|
+
"/robots.txt",
|
|
2176
|
+
"/sitemap.xml"
|
|
2177
|
+
];
|
|
2178
|
+
const skipPaths = [...defaultSkipPaths, ...config.skipPaths || []];
|
|
2179
|
+
const failOpen = config.failOpen ?? true;
|
|
2180
|
+
return async function middleware(request) {
|
|
2181
|
+
const path = request.nextUrl.pathname;
|
|
2182
|
+
const startTime = Date.now();
|
|
2183
|
+
if (shouldSkipPath(path, skipPaths)) {
|
|
2184
|
+
return NextResponse.next();
|
|
2185
|
+
}
|
|
2186
|
+
if (!shouldIncludePath(path, config.includePaths)) {
|
|
2187
|
+
return NextResponse.next();
|
|
2188
|
+
}
|
|
2189
|
+
try {
|
|
2190
|
+
const result = await getClient().enforce({
|
|
2191
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
2192
|
+
userAgent: request.headers.get("user-agent") || void 0,
|
|
2193
|
+
ipAddress: request.ip || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || void 0,
|
|
2194
|
+
path,
|
|
2195
|
+
url: request.url,
|
|
2196
|
+
method: request.method,
|
|
2197
|
+
requestId: request.headers.get("x-request-id") || void 0,
|
|
2198
|
+
options: {
|
|
2199
|
+
includeDetectionResult: config.debug
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
if (!result.success || !result.data) {
|
|
2203
|
+
if (config.debug) {
|
|
2204
|
+
console.warn("[AgentShield] API error:", result.error);
|
|
2205
|
+
}
|
|
2206
|
+
if (failOpen) {
|
|
2207
|
+
return NextResponse.next();
|
|
2208
|
+
}
|
|
2209
|
+
return NextResponse.json(
|
|
2210
|
+
{ error: "Security check failed", code: "API_ERROR" },
|
|
2211
|
+
{ status: 503 }
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
const decision = result.data.decision;
|
|
2215
|
+
if (config.debug) {
|
|
2216
|
+
console.log("[AgentShield] Decision:", {
|
|
2217
|
+
path,
|
|
2218
|
+
action: decision.action,
|
|
2219
|
+
isAgent: decision.isAgent,
|
|
2220
|
+
confidence: decision.confidence,
|
|
2221
|
+
agentName: decision.agentName,
|
|
2222
|
+
processingTimeMs: Date.now() - startTime
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
if (decision.isAgent && config.onAgentDetected) {
|
|
2226
|
+
await config.onAgentDetected(request, decision);
|
|
2227
|
+
}
|
|
2228
|
+
switch (decision.action) {
|
|
2229
|
+
case "block": {
|
|
2230
|
+
if (config.customBlockedResponse) {
|
|
2231
|
+
return config.customBlockedResponse(request, decision);
|
|
2232
|
+
}
|
|
2233
|
+
if (config.onBlock === "redirect") {
|
|
2234
|
+
return buildRedirectResponse(request, decision, config);
|
|
2235
|
+
}
|
|
2236
|
+
return buildBlockedResponse(decision, config);
|
|
2237
|
+
}
|
|
2238
|
+
case "redirect": {
|
|
2239
|
+
return buildRedirectResponse(request, decision, config);
|
|
2240
|
+
}
|
|
2241
|
+
case "challenge": {
|
|
2242
|
+
return buildRedirectResponse(request, decision, config);
|
|
2243
|
+
}
|
|
2244
|
+
case "log":
|
|
2245
|
+
case "allow":
|
|
2246
|
+
default: {
|
|
2247
|
+
const response = NextResponse.next();
|
|
2248
|
+
if (decision.isAgent) {
|
|
2249
|
+
response.headers.set("X-AgentShield-Detected", "true");
|
|
2250
|
+
response.headers.set("X-AgentShield-Confidence", decision.confidence.toString());
|
|
2251
|
+
if (decision.agentName) {
|
|
2252
|
+
response.headers.set("X-AgentShield-Agent", decision.agentName);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return response;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
if (config.debug) {
|
|
2260
|
+
console.error("[AgentShield] Middleware error:", error);
|
|
2261
|
+
}
|
|
2262
|
+
if (failOpen) {
|
|
2263
|
+
return NextResponse.next();
|
|
2264
|
+
}
|
|
2265
|
+
return NextResponse.json(
|
|
2266
|
+
{ error: "Security check failed", code: "MIDDLEWARE_ERROR" },
|
|
2267
|
+
{ status: 503 }
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
var agentShieldMiddleware = withAgentShield();
|
|
2273
|
+
|
|
1711
2274
|
// src/index.ts
|
|
1712
2275
|
var VERSION = "0.1.0";
|
|
1713
2276
|
/**
|
|
@@ -1716,6 +2279,6 @@ var VERSION = "0.1.0";
|
|
|
1716
2279
|
* @license MIT OR Apache-2.0
|
|
1717
2280
|
*/
|
|
1718
2281
|
|
|
1719
|
-
export { EdgeSessionTracker, StatelessSessionChecker, VERSION, createAgentShieldMiddleware2 as createAgentShieldMiddleware, createAgentShieldMiddleware as createAgentShieldMiddlewareBase, createEnhancedAgentShieldMiddleware, createAgentShieldMiddleware2 as createMiddleware };
|
|
2282
|
+
export { AgentShieldClient, EdgeSessionTracker, StatelessSessionChecker, VERSION, agentShieldMiddleware, createAgentShieldMiddleware2 as createAgentShieldMiddleware, createAgentShieldMiddleware as createAgentShieldMiddlewareBase, createEnhancedAgentShieldMiddleware, createAgentShieldMiddleware2 as createMiddleware, getAgentShieldClient, resetAgentShieldClient, withAgentShield };
|
|
1720
2283
|
//# sourceMappingURL=index.mjs.map
|
|
1721
2284
|
//# sourceMappingURL=index.mjs.map
|