@kya-os/agentshield-nextjs 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/api-client.d.mts +145 -0
- package/dist/api-client.d.ts +145 -0
- package/dist/api-client.js +130 -0
- package/dist/api-client.js.map +1 -0
- package/dist/api-client.mjs +126 -0
- package/dist/api-client.mjs.map +1 -0
- package/dist/api-middleware.d.mts +118 -0
- package/dist/api-middleware.d.ts +118 -0
- package/dist/api-middleware.js +295 -0
- package/dist/api-middleware.js.map +1 -0
- package/dist/api-middleware.mjs +292 -0
- package/dist/api-middleware.mjs.map +1 -0
- package/dist/create-middleware.d.mts +2 -1
- package/dist/create-middleware.d.ts +2 -1
- package/dist/create-middleware.js +474 -200
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +454 -200
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge/index.d.mts +110 -0
- package/dist/edge/index.d.ts +110 -0
- package/dist/edge/index.js +253 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/edge/index.mjs +251 -0
- package/dist/edge/index.mjs.map +1 -0
- package/dist/edge-detector-wrapper.d.mts +6 -15
- package/dist/edge-detector-wrapper.d.ts +6 -15
- package/dist/edge-detector-wrapper.js +314 -95
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +294 -95
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/edge-runtime-loader.d.mts +1 -1
- package/dist/edge-runtime-loader.d.ts +1 -1
- package/dist/edge-runtime-loader.js +10 -25
- package/dist/edge-runtime-loader.js.map +1 -1
- package/dist/edge-runtime-loader.mjs +11 -23
- package/dist/edge-runtime-loader.mjs.map +1 -1
- package/dist/edge-wasm-middleware.js +2 -1
- package/dist/edge-wasm-middleware.js.map +1 -1
- package/dist/edge-wasm-middleware.mjs +2 -1
- package/dist/edge-wasm-middleware.mjs.map +1 -1
- package/dist/enhanced-middleware.d.mts +153 -0
- package/dist/enhanced-middleware.d.ts +153 -0
- package/dist/enhanced-middleware.js +1074 -0
- package/dist/enhanced-middleware.js.map +1 -0
- package/dist/enhanced-middleware.mjs +1072 -0
- package/dist/enhanced-middleware.mjs.map +1 -0
- package/dist/index.d.mts +8 -153
- package/dist/index.d.ts +8 -153
- package/dist/index.js +1390 -680
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1370 -685
- 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 +43 -21
- 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,379 +1,19 @@
|
|
|
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';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
chatgpt: [
|
|
8
|
-
{
|
|
9
|
-
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
10
|
-
// ChatGPT's current Ed25519 public key (base64)
|
|
11
|
-
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
12
|
-
validFrom: (/* @__PURE__ */ new Date("2025-01-01")).getTime() / 1e3,
|
|
13
|
-
validUntil: (/* @__PURE__ */ new Date("2025-04-11")).getTime() / 1e3
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
};
|
|
17
|
-
function parseSignatureInput(signatureInput) {
|
|
18
|
-
try {
|
|
19
|
-
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
20
|
-
if (!match) return null;
|
|
21
|
-
const [, headersList, params] = match;
|
|
22
|
-
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
23
|
-
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
24
|
-
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
25
|
-
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
26
|
-
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
27
|
-
return {
|
|
28
|
-
keyid: keyidMatch[1],
|
|
29
|
-
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
30
|
-
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
31
|
-
signedHeaders
|
|
32
|
-
};
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
39
|
-
const components = [];
|
|
40
|
-
for (const headerName of signedHeaders) {
|
|
41
|
-
let value;
|
|
42
|
-
switch (headerName) {
|
|
43
|
-
case "@method":
|
|
44
|
-
value = method.toUpperCase();
|
|
45
|
-
break;
|
|
46
|
-
case "@path":
|
|
47
|
-
value = path;
|
|
48
|
-
break;
|
|
49
|
-
case "@authority":
|
|
50
|
-
value = headers["host"] || headers["Host"] || "";
|
|
51
|
-
break;
|
|
52
|
-
default:
|
|
53
|
-
const key = Object.keys(headers).find(
|
|
54
|
-
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
55
|
-
);
|
|
56
|
-
value = key ? headers[key] || "" : "";
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
components.push(`"${headerName}": ${value}`);
|
|
60
|
-
}
|
|
61
|
-
return components.join("\n");
|
|
62
|
-
}
|
|
63
|
-
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
64
|
-
try {
|
|
65
|
-
const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
|
|
66
|
-
const signatureBytes = Uint8Array.from(atob(signatureBase64), (c) => c.charCodeAt(0));
|
|
67
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
68
|
-
if (publicKeyBytes.length !== 32) {
|
|
69
|
-
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
if (signatureBytes.length !== 64) {
|
|
73
|
-
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
const publicKey = await crypto.subtle.importKey(
|
|
77
|
-
"raw",
|
|
78
|
-
publicKeyBytes,
|
|
79
|
-
{
|
|
80
|
-
name: "Ed25519",
|
|
81
|
-
namedCurve: "Ed25519"
|
|
82
|
-
},
|
|
83
|
-
false,
|
|
84
|
-
["verify"]
|
|
85
|
-
);
|
|
86
|
-
const isValid = await crypto.subtle.verify(
|
|
87
|
-
"Ed25519",
|
|
88
|
-
publicKey,
|
|
89
|
-
signatureBytes,
|
|
90
|
-
messageBytes
|
|
91
|
-
);
|
|
92
|
-
return isValid;
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error("[Signature] Ed25519 verification failed:", error);
|
|
95
|
-
if (typeof window === "undefined") {
|
|
96
|
-
try {
|
|
97
|
-
console.warn("[Signature] Ed25519 not supported in this environment");
|
|
98
|
-
return false;
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
async function verifyAgentSignature(method, path, headers) {
|
|
107
|
-
const signature = headers["signature"] || headers["Signature"];
|
|
108
|
-
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
109
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
110
|
-
if (!signature || !signatureInput) {
|
|
111
|
-
return {
|
|
112
|
-
isValid: false,
|
|
113
|
-
confidence: 0,
|
|
114
|
-
reason: "No signature headers present",
|
|
115
|
-
verificationMethod: "none"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
const parsed = parseSignatureInput(signatureInput);
|
|
119
|
-
if (!parsed) {
|
|
120
|
-
return {
|
|
121
|
-
isValid: false,
|
|
122
|
-
confidence: 0,
|
|
123
|
-
reason: "Invalid Signature-Input header",
|
|
124
|
-
verificationMethod: "none"
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
if (parsed.created) {
|
|
128
|
-
const now2 = Math.floor(Date.now() / 1e3);
|
|
129
|
-
const age = now2 - parsed.created;
|
|
130
|
-
if (age > 300) {
|
|
131
|
-
return {
|
|
132
|
-
isValid: false,
|
|
133
|
-
confidence: 0,
|
|
134
|
-
reason: "Signature expired (older than 5 minutes)",
|
|
135
|
-
verificationMethod: "none"
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (age < -30) {
|
|
139
|
-
return {
|
|
140
|
-
isValid: false,
|
|
141
|
-
confidence: 0,
|
|
142
|
-
reason: "Signature timestamp is in the future",
|
|
143
|
-
verificationMethod: "none"
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
let agent;
|
|
148
|
-
let knownKeys;
|
|
149
|
-
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
150
|
-
agent = "ChatGPT";
|
|
151
|
-
knownKeys = KNOWN_KEYS.chatgpt;
|
|
152
|
-
}
|
|
153
|
-
if (!agent || !knownKeys) {
|
|
154
|
-
return {
|
|
155
|
-
isValid: false,
|
|
156
|
-
confidence: 0,
|
|
157
|
-
reason: "Unknown signature agent",
|
|
158
|
-
verificationMethod: "none"
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
162
|
-
if (!key) {
|
|
163
|
-
return {
|
|
164
|
-
isValid: false,
|
|
165
|
-
confidence: 0,
|
|
166
|
-
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
167
|
-
verificationMethod: "none"
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
171
|
-
if (now < key.validFrom || now > key.validUntil) {
|
|
172
|
-
return {
|
|
173
|
-
isValid: false,
|
|
174
|
-
confidence: 0,
|
|
175
|
-
reason: "Key is not valid at current time",
|
|
176
|
-
verificationMethod: "none"
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
180
|
-
let signatureValue = signature;
|
|
181
|
-
if (signatureValue.startsWith("sig1=:")) {
|
|
182
|
-
signatureValue = signatureValue.substring(6);
|
|
183
|
-
}
|
|
184
|
-
if (signatureValue.endsWith(":")) {
|
|
185
|
-
signatureValue = signatureValue.slice(0, -1);
|
|
186
|
-
}
|
|
187
|
-
const isValid = await verifyEd25519Signature(
|
|
188
|
-
key.publicKey,
|
|
189
|
-
signatureValue,
|
|
190
|
-
signatureBase
|
|
191
|
-
);
|
|
192
|
-
if (isValid) {
|
|
193
|
-
return {
|
|
194
|
-
isValid: true,
|
|
195
|
-
agent,
|
|
196
|
-
keyid: parsed.keyid,
|
|
197
|
-
confidence: 1,
|
|
198
|
-
// 100% confidence for valid signature
|
|
199
|
-
verificationMethod: "signature"
|
|
200
|
-
};
|
|
201
|
-
} else {
|
|
202
|
-
return {
|
|
203
|
-
isValid: false,
|
|
204
|
-
confidence: 0,
|
|
205
|
-
reason: "Signature verification failed",
|
|
206
|
-
verificationMethod: "none"
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
function hasSignatureHeaders(headers) {
|
|
211
|
-
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
212
|
-
}
|
|
213
|
-
function isChatGPTSignature(headers) {
|
|
214
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
215
|
-
return signatureAgent === '"https://chatgpt.com"' || (signatureAgent?.includes("chatgpt.com") || false);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/edge-detector-wrapper.ts
|
|
219
|
-
var AI_AGENT_PATTERNS = [
|
|
220
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
221
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
222
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
223
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
224
|
-
{ pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
|
|
225
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
226
|
-
// Fallback
|
|
227
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
228
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
229
|
-
];
|
|
230
|
-
var CLOUD_PROVIDERS = {
|
|
231
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
232
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
233
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
234
|
-
};
|
|
235
|
-
var EdgeAgentDetector = class {
|
|
236
|
-
async analyze(input) {
|
|
237
|
-
const reasons = [];
|
|
238
|
-
let detectedAgent;
|
|
239
|
-
let verificationMethod;
|
|
240
|
-
let confidence = 0;
|
|
241
|
-
const headers = input.headers || {};
|
|
242
|
-
const normalizedHeaders = {};
|
|
243
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
244
|
-
normalizedHeaders[key.toLowerCase()] = value;
|
|
245
|
-
}
|
|
246
|
-
if (hasSignatureHeaders(headers)) {
|
|
247
|
-
try {
|
|
248
|
-
const signatureResult = await verifyAgentSignature(
|
|
249
|
-
input.method || "GET",
|
|
250
|
-
input.url || "/",
|
|
251
|
-
headers
|
|
252
|
-
);
|
|
253
|
-
if (signatureResult.isValid) {
|
|
254
|
-
confidence = signatureResult.confidence;
|
|
255
|
-
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
256
|
-
if (signatureResult.agent) {
|
|
257
|
-
detectedAgent = {
|
|
258
|
-
type: signatureResult.agent.toLowerCase(),
|
|
259
|
-
name: signatureResult.agent
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
verificationMethod = signatureResult.verificationMethod;
|
|
263
|
-
if (signatureResult.keyid) {
|
|
264
|
-
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
confidence = Math.max(confidence, 0.3);
|
|
268
|
-
reasons.push("invalid_signature");
|
|
269
|
-
if (signatureResult.reason) {
|
|
270
|
-
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
271
|
-
}
|
|
272
|
-
if (isChatGPTSignature(headers)) {
|
|
273
|
-
reasons.push("claims_chatgpt");
|
|
274
|
-
detectedAgent = { type: "chatgpt", name: "ChatGPT (unverified)" };
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} catch (error) {
|
|
278
|
-
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
279
|
-
confidence = Math.max(confidence, 0.2);
|
|
280
|
-
reasons.push("signature_verification_error");
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
284
|
-
if (userAgent) {
|
|
285
|
-
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
286
|
-
if (pattern.test(userAgent)) {
|
|
287
|
-
const highConfidenceAgents = [
|
|
288
|
-
"chatgpt",
|
|
289
|
-
"claude",
|
|
290
|
-
"perplexity",
|
|
291
|
-
"anthropic"
|
|
292
|
-
];
|
|
293
|
-
const patternConfidence = highConfidenceAgents.includes(type) ? 0.85 : 0.5;
|
|
294
|
-
confidence = Math.max(confidence, patternConfidence);
|
|
295
|
-
reasons.push(`known_pattern:${type}`);
|
|
296
|
-
if (!detectedAgent) {
|
|
297
|
-
detectedAgent = { type, name };
|
|
298
|
-
verificationMethod = "pattern";
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const aiHeaders = [
|
|
305
|
-
"openai-conversation-id",
|
|
306
|
-
"openai-ephemeral-user-id",
|
|
307
|
-
"anthropic-client-id",
|
|
308
|
-
"x-goog-api-client",
|
|
309
|
-
"x-ms-copilot-id"
|
|
310
|
-
];
|
|
311
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
312
|
-
(header) => normalizedHeaders[header]
|
|
313
|
-
);
|
|
314
|
-
if (foundAiHeaders.length > 0) {
|
|
315
|
-
confidence = Math.max(confidence, 0.6);
|
|
316
|
-
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
317
|
-
}
|
|
318
|
-
const ip = input.ip || input.ipAddress;
|
|
319
|
-
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
320
|
-
for (const [provider, prefixes] of Object.entries(CLOUD_PROVIDERS)) {
|
|
321
|
-
if (prefixes.some((prefix) => ip.startsWith(prefix))) {
|
|
322
|
-
confidence = Math.max(confidence, 0.4);
|
|
323
|
-
reasons.push(`cloud_provider:${provider}`);
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (reasons.length > 2 && confidence < 1) {
|
|
329
|
-
confidence = Math.min(confidence * 1.2, 0.95);
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
isAgent: confidence > 0.3,
|
|
333
|
-
confidence,
|
|
334
|
-
...detectedAgent && { detectedAgent },
|
|
335
|
-
reasons,
|
|
336
|
-
...verificationMethod && { verificationMethod },
|
|
337
|
-
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 0.8 ? "medium" : "high",
|
|
338
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
339
|
-
};
|
|
340
|
-
}
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
341
10
|
};
|
|
342
|
-
var
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
constructor(_config) {
|
|
346
|
-
this.detector = new EdgeAgentDetector();
|
|
347
|
-
}
|
|
348
|
-
async analyze(input) {
|
|
349
|
-
const result = await this.detector.analyze(input);
|
|
350
|
-
if (result.isAgent && this.events.has("agent.detected")) {
|
|
351
|
-
const handlers = this.events.get("agent.detected") || [];
|
|
352
|
-
handlers.forEach((handler) => handler(result, input));
|
|
353
|
-
}
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
on(event, handler) {
|
|
357
|
-
if (!this.events.has(event)) {
|
|
358
|
-
this.events.set(event, []);
|
|
359
|
-
}
|
|
360
|
-
this.events.get(event).push(handler);
|
|
361
|
-
}
|
|
362
|
-
emit(event, ...args) {
|
|
363
|
-
const handlers = this.events.get(event) || [];
|
|
364
|
-
handlers.forEach((handler) => handler(...args));
|
|
365
|
-
}
|
|
366
|
-
async init() {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
369
14
|
};
|
|
370
15
|
|
|
371
16
|
// src/wasm-loader.ts
|
|
372
|
-
var wasmInstance = null;
|
|
373
|
-
var wasmExports = null;
|
|
374
|
-
var initPromise = null;
|
|
375
|
-
var WASM_PATH = "/wasm/agentshield_wasm_bg.wasm";
|
|
376
|
-
var baseUrl = null;
|
|
377
17
|
function setWasmBaseUrl(url) {
|
|
378
18
|
baseUrl = url;
|
|
379
19
|
}
|
|
@@ -408,26 +48,31 @@ async function initWasm() {
|
|
|
408
48
|
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
409
49
|
}
|
|
410
50
|
const streamResponse = response2.clone();
|
|
411
|
-
const { instance } = await WebAssembly.instantiateStreaming(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
console.log("WASM:", ptr, len);
|
|
417
|
-
},
|
|
418
|
-
__wbindgen_throw: (ptr, len) => {
|
|
419
|
-
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);
|
|
420
56
|
}
|
|
57
|
+
},
|
|
58
|
+
__wbindgen_throw: (ptr, len) => {
|
|
59
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
421
60
|
}
|
|
422
61
|
}
|
|
423
|
-
);
|
|
62
|
+
});
|
|
424
63
|
wasmInstance = instance;
|
|
425
64
|
wasmExports = instance.exports;
|
|
426
|
-
|
|
65
|
+
if (process.env.NODE_ENV !== "production") {
|
|
66
|
+
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
67
|
+
}
|
|
427
68
|
return;
|
|
428
69
|
} catch (streamError) {
|
|
429
70
|
if (!controller.signal.aborted) {
|
|
430
|
-
|
|
71
|
+
if (process.env.NODE_ENV !== "production") {
|
|
72
|
+
console.debug(
|
|
73
|
+
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
431
76
|
} else {
|
|
432
77
|
throw streamError;
|
|
433
78
|
}
|
|
@@ -443,7 +88,9 @@ async function initWasm() {
|
|
|
443
88
|
const imports = {
|
|
444
89
|
wbg: {
|
|
445
90
|
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
446
|
-
|
|
91
|
+
if (process.env.NODE_ENV !== "production") {
|
|
92
|
+
console.debug("WASM:", ptr, len);
|
|
93
|
+
}
|
|
447
94
|
},
|
|
448
95
|
__wbindgen_throw: (ptr, len) => {
|
|
449
96
|
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
@@ -452,12 +99,20 @@ async function initWasm() {
|
|
|
452
99
|
};
|
|
453
100
|
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
454
101
|
wasmExports = wasmInstance.exports;
|
|
455
|
-
|
|
102
|
+
if (process.env.NODE_ENV !== "production") {
|
|
103
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
104
|
+
}
|
|
456
105
|
} catch (fetchError) {
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
);
|
|
459
111
|
} else {
|
|
460
|
-
console.warn(
|
|
112
|
+
console.warn(
|
|
113
|
+
"[AgentShield] Failed to fetch WASM file:",
|
|
114
|
+
error.message || "Unknown error"
|
|
115
|
+
);
|
|
461
116
|
}
|
|
462
117
|
wasmExports = null;
|
|
463
118
|
}
|
|
@@ -465,122 +120,765 @@ async function initWasm() {
|
|
|
465
120
|
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
466
121
|
wasmExports = null;
|
|
467
122
|
}
|
|
468
|
-
})();
|
|
469
|
-
await initPromise;
|
|
470
|
-
return !!wasmExports;
|
|
123
|
+
})();
|
|
124
|
+
await initPromise;
|
|
125
|
+
return !!wasmExports;
|
|
126
|
+
}
|
|
127
|
+
async function detectAgentWithWasm(userAgent, headers, ipAddress) {
|
|
128
|
+
const initialized = await initWasm();
|
|
129
|
+
if (!initialized || !wasmExports) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const headersJson = JSON.stringify(headers);
|
|
134
|
+
if (typeof wasmExports.detect_agent === "function") {
|
|
135
|
+
const result = wasmExports.detect_agent(
|
|
136
|
+
userAgent || "",
|
|
137
|
+
headersJson,
|
|
138
|
+
ipAddress || "",
|
|
139
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
isAgent: result.is_agent || false,
|
|
143
|
+
confidence: result.confidence || 0,
|
|
144
|
+
agent: result.agent,
|
|
145
|
+
verificationMethod: result.verification_method || "wasm",
|
|
146
|
+
riskLevel: result.risk_level || "low"
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
console.warn("[AgentShield] WASM exports do not include detect_agent function");
|
|
150
|
+
return null;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[AgentShield] WASM detection failed:", error);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function getWasmVersion() {
|
|
157
|
+
const initialized = await initWasm();
|
|
158
|
+
if (!initialized || !wasmExports) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (typeof wasmExports.version === "function") {
|
|
162
|
+
return wasmExports.version();
|
|
163
|
+
}
|
|
164
|
+
return "unknown";
|
|
165
|
+
}
|
|
166
|
+
async function isWasmAvailable() {
|
|
167
|
+
try {
|
|
168
|
+
const initialized = await initWasm();
|
|
169
|
+
if (!initialized) return false;
|
|
170
|
+
const version = await getWasmVersion();
|
|
171
|
+
return version !== null;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
var wasmInstance, wasmExports, initPromise, WASM_PATH, baseUrl;
|
|
177
|
+
var init_wasm_loader = __esm({
|
|
178
|
+
"src/wasm-loader.ts"() {
|
|
179
|
+
wasmInstance = null;
|
|
180
|
+
wasmExports = null;
|
|
181
|
+
initPromise = null;
|
|
182
|
+
WASM_PATH = "/wasm/agentshield_wasm_bg.wasm";
|
|
183
|
+
baseUrl = null;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// src/edge-detector-with-wasm.ts
|
|
188
|
+
var edge_detector_with_wasm_exports = {};
|
|
189
|
+
__export(edge_detector_with_wasm_exports, {
|
|
190
|
+
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
191
|
+
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
192
|
+
});
|
|
193
|
+
var rules2, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
194
|
+
var init_edge_detector_with_wasm = __esm({
|
|
195
|
+
"src/edge-detector-with-wasm.ts"() {
|
|
196
|
+
init_wasm_loader();
|
|
197
|
+
rules2 = loadRulesSync();
|
|
198
|
+
EdgeAgentDetectorWithWasm = class {
|
|
199
|
+
constructor(enableWasm = true) {
|
|
200
|
+
this.enableWasm = enableWasm;
|
|
201
|
+
this.rules = rules2;
|
|
202
|
+
}
|
|
203
|
+
wasmEnabled = false;
|
|
204
|
+
initPromise = null;
|
|
205
|
+
baseUrl = null;
|
|
206
|
+
rules;
|
|
207
|
+
/**
|
|
208
|
+
* Set the base URL for WASM loading in Edge Runtime
|
|
209
|
+
*/
|
|
210
|
+
setBaseUrl(url) {
|
|
211
|
+
this.baseUrl = url;
|
|
212
|
+
setWasmBaseUrl(url);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Initialize the detector (including WASM if enabled)
|
|
216
|
+
*/
|
|
217
|
+
async init() {
|
|
218
|
+
if (!this.enableWasm) {
|
|
219
|
+
this.wasmEnabled = false;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (this.initPromise) {
|
|
223
|
+
await this.initPromise;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.initPromise = (async () => {
|
|
227
|
+
try {
|
|
228
|
+
const wasmAvailable = await isWasmAvailable();
|
|
229
|
+
if (wasmAvailable) {
|
|
230
|
+
if (this.baseUrl) {
|
|
231
|
+
setWasmBaseUrl(this.baseUrl);
|
|
232
|
+
}
|
|
233
|
+
await initWasm();
|
|
234
|
+
this.wasmEnabled = true;
|
|
235
|
+
} else {
|
|
236
|
+
this.wasmEnabled = false;
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
240
|
+
this.wasmEnabled = false;
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
await this.initPromise;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Pattern-based detection (fallback)
|
|
247
|
+
*/
|
|
248
|
+
async patternDetection(input) {
|
|
249
|
+
const reasons = [];
|
|
250
|
+
let detectedAgent;
|
|
251
|
+
let verificationMethod;
|
|
252
|
+
let confidence = 0;
|
|
253
|
+
const headers = input.headers || {};
|
|
254
|
+
const normalizedHeaders = {};
|
|
255
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
256
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
257
|
+
}
|
|
258
|
+
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
259
|
+
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
260
|
+
if (signatureAgent?.includes("chatgpt.com")) {
|
|
261
|
+
confidence = 85;
|
|
262
|
+
reasons.push("signature_agent:chatgpt");
|
|
263
|
+
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
264
|
+
verificationMethod = "signature";
|
|
265
|
+
} else if (signaturePresent) {
|
|
266
|
+
confidence = Math.max(confidence, 40);
|
|
267
|
+
reasons.push("signature_present");
|
|
268
|
+
}
|
|
269
|
+
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
270
|
+
if (userAgent) {
|
|
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}`);
|
|
281
|
+
if (!detectedAgent) {
|
|
282
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
283
|
+
verificationMethod = "pattern";
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
290
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
291
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
292
|
+
);
|
|
293
|
+
if (foundAiHeaders.length > 0) {
|
|
294
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
295
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
296
|
+
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
297
|
+
}
|
|
298
|
+
const ip = input.ip || input.ipAddress;
|
|
299
|
+
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
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));
|
|
317
|
+
reasons.push(`cloud_provider:${provider}`);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (reasons.length > 2) {
|
|
323
|
+
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
324
|
+
}
|
|
325
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
326
|
+
return {
|
|
327
|
+
isAgent: confidence > 30,
|
|
328
|
+
// 30% threshold
|
|
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
|
|
333
|
+
...detectedAgent && { detectedAgent },
|
|
334
|
+
reasons,
|
|
335
|
+
...verificationMethod && {
|
|
336
|
+
verificationMethod: mapVerificationMethod(verificationMethod)
|
|
337
|
+
},
|
|
338
|
+
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
339
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Analyze request with WASM enhancement when available
|
|
344
|
+
*/
|
|
345
|
+
async analyze(input) {
|
|
346
|
+
await this.init();
|
|
347
|
+
if (this.wasmEnabled) {
|
|
348
|
+
try {
|
|
349
|
+
const wasmResult = await detectAgentWithWasm(
|
|
350
|
+
input.userAgent || input.headers?.["user-agent"],
|
|
351
|
+
input.headers || {},
|
|
352
|
+
input.ip || input.ipAddress
|
|
353
|
+
);
|
|
354
|
+
if (wasmResult) {
|
|
355
|
+
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
356
|
+
return {
|
|
357
|
+
isAgent: wasmResult.isAgent,
|
|
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
|
|
362
|
+
...detectedAgent && { detectedAgent },
|
|
363
|
+
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
364
|
+
verificationMethod: mapVerificationMethod(wasmResult.verificationMethod),
|
|
365
|
+
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
366
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error("[AgentShield] WASM detection error:", error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const patternResult = await this.patternDetection(input);
|
|
374
|
+
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
375
|
+
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
376
|
+
patternResult.reasons.push("wasm_enhanced");
|
|
377
|
+
}
|
|
378
|
+
return patternResult;
|
|
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
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Map agent names from WASM to consistent format
|
|
420
|
+
*/
|
|
421
|
+
mapAgentName(agent) {
|
|
422
|
+
const lowerAgent = agent.toLowerCase();
|
|
423
|
+
if (lowerAgent.includes("chatgpt")) {
|
|
424
|
+
return { type: "chatgpt", name: "ChatGPT" };
|
|
425
|
+
} else if (lowerAgent.includes("claude")) {
|
|
426
|
+
return { type: "claude", name: "Claude" };
|
|
427
|
+
} else if (lowerAgent.includes("perplexity")) {
|
|
428
|
+
return { type: "perplexity", name: "Perplexity" };
|
|
429
|
+
} else if (lowerAgent.includes("bing")) {
|
|
430
|
+
return { type: "bing", name: "Bing AI" };
|
|
431
|
+
} else if (lowerAgent.includes("anthropic")) {
|
|
432
|
+
return { type: "anthropic", name: "Anthropic" };
|
|
433
|
+
}
|
|
434
|
+
return { type: "unknown", name: agent };
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
EdgeAgentDetectorWrapperWithWasm = class {
|
|
438
|
+
detector;
|
|
439
|
+
events = /* @__PURE__ */ new Map();
|
|
440
|
+
constructor(config) {
|
|
441
|
+
this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true);
|
|
442
|
+
if (config?.baseUrl) {
|
|
443
|
+
this.detector.setBaseUrl(config.baseUrl);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
setBaseUrl(url) {
|
|
447
|
+
this.detector.setBaseUrl(url);
|
|
448
|
+
}
|
|
449
|
+
async analyze(input) {
|
|
450
|
+
const result = await this.detector.analyze(input);
|
|
451
|
+
if (result.isAgent && this.events.has("agent.detected")) {
|
|
452
|
+
const handlers = this.events.get("agent.detected") || [];
|
|
453
|
+
handlers.forEach((handler) => handler(result, input));
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
on(event, handler) {
|
|
458
|
+
if (!this.events.has(event)) {
|
|
459
|
+
this.events.set(event, []);
|
|
460
|
+
}
|
|
461
|
+
this.events.get(event).push(handler);
|
|
462
|
+
}
|
|
463
|
+
emit(event, ...args) {
|
|
464
|
+
const handlers = this.events.get(event) || [];
|
|
465
|
+
handlers.forEach((handler) => handler(...args));
|
|
466
|
+
}
|
|
467
|
+
async init() {
|
|
468
|
+
await this.detector.init();
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// src/wasm-confidence.ts
|
|
475
|
+
var wasm_confidence_exports = {};
|
|
476
|
+
__export(wasm_confidence_exports, {
|
|
477
|
+
checkWasmAvailability: () => checkWasmAvailability,
|
|
478
|
+
getVerificationMethod: () => getVerificationMethod,
|
|
479
|
+
getWasmConfidenceBoost: () => getWasmConfidenceBoost,
|
|
480
|
+
shouldIndicateWasmVerification: () => shouldIndicateWasmVerification
|
|
481
|
+
});
|
|
482
|
+
async function checkWasmAvailability() {
|
|
483
|
+
try {
|
|
484
|
+
if (typeof WebAssembly === "undefined") {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
if (!WebAssembly.instantiate || !WebAssembly.Module) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
return true;
|
|
491
|
+
} catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function shouldIndicateWasmVerification(confidence) {
|
|
496
|
+
return confidence >= 85 && confidence < 100;
|
|
497
|
+
}
|
|
498
|
+
function getWasmConfidenceBoost(baseConfidence, reasons = []) {
|
|
499
|
+
if (reasons.some(
|
|
500
|
+
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
501
|
+
)) {
|
|
502
|
+
return 100;
|
|
503
|
+
}
|
|
504
|
+
if (baseConfidence >= 85) {
|
|
505
|
+
return 95;
|
|
506
|
+
}
|
|
507
|
+
if (baseConfidence >= 70) {
|
|
508
|
+
return Math.min(baseConfidence * 1.1, 90);
|
|
509
|
+
}
|
|
510
|
+
return baseConfidence;
|
|
511
|
+
}
|
|
512
|
+
function getVerificationMethod(confidence, reasons = []) {
|
|
513
|
+
if (reasons.some(
|
|
514
|
+
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
515
|
+
)) {
|
|
516
|
+
return "signature";
|
|
517
|
+
}
|
|
518
|
+
if (shouldIndicateWasmVerification(confidence)) {
|
|
519
|
+
return "wasm-enhanced";
|
|
520
|
+
}
|
|
521
|
+
return "pattern";
|
|
522
|
+
}
|
|
523
|
+
var init_wasm_confidence = __esm({
|
|
524
|
+
"src/wasm-confidence.ts"() {
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
528
|
+
var KNOWN_KEYS = {
|
|
529
|
+
chatgpt: [
|
|
530
|
+
{
|
|
531
|
+
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
532
|
+
// ChatGPT's current Ed25519 public key (base64)
|
|
533
|
+
// Source: https://chatgpt.com/.well-known/http-message-signatures-directory
|
|
534
|
+
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
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)
|
|
541
|
+
}
|
|
542
|
+
]
|
|
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
|
+
}
|
|
471
588
|
}
|
|
472
|
-
async function
|
|
473
|
-
|
|
474
|
-
|
|
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");
|
|
475
599
|
return null;
|
|
476
600
|
}
|
|
477
601
|
try {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
riskLevel: result.risk_level || "low"
|
|
492
|
-
};
|
|
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;
|
|
493
615
|
}
|
|
494
|
-
|
|
495
|
-
|
|
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;
|
|
496
626
|
} catch (error) {
|
|
497
|
-
console.
|
|
627
|
+
console.warn("[Signature] Error fetching keys from API, using fallback", {
|
|
628
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
629
|
+
agent
|
|
630
|
+
});
|
|
498
631
|
return null;
|
|
499
632
|
}
|
|
500
633
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
+
}
|
|
647
|
+
function parseSignatureInput(signatureInput) {
|
|
648
|
+
try {
|
|
649
|
+
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
650
|
+
if (!match) return null;
|
|
651
|
+
const [, headersList, params] = match;
|
|
652
|
+
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
653
|
+
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
654
|
+
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
655
|
+
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
656
|
+
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
657
|
+
return {
|
|
658
|
+
keyid: keyidMatch[1],
|
|
659
|
+
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
660
|
+
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
661
|
+
signedHeaders
|
|
662
|
+
};
|
|
663
|
+
} catch (error) {
|
|
664
|
+
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
504
665
|
return null;
|
|
505
666
|
}
|
|
506
|
-
|
|
507
|
-
|
|
667
|
+
}
|
|
668
|
+
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
669
|
+
const components = [];
|
|
670
|
+
for (const headerName of signedHeaders) {
|
|
671
|
+
let value;
|
|
672
|
+
switch (headerName) {
|
|
673
|
+
case "@method":
|
|
674
|
+
value = method.toUpperCase();
|
|
675
|
+
break;
|
|
676
|
+
case "@path":
|
|
677
|
+
value = path;
|
|
678
|
+
break;
|
|
679
|
+
case "@authority":
|
|
680
|
+
value = headers["host"] || headers["Host"] || "";
|
|
681
|
+
break;
|
|
682
|
+
default: {
|
|
683
|
+
const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
|
|
684
|
+
value = key ? headers[key] || "" : "";
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
components.push(`"${headerName}": ${value}`);
|
|
508
689
|
}
|
|
509
|
-
return "
|
|
690
|
+
return components.join("\n");
|
|
510
691
|
}
|
|
511
|
-
|
|
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
|
+
}
|
|
701
|
+
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
512
702
|
try {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
703
|
+
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
704
|
+
const signatureBytes = base64ToBytes(signatureBase64);
|
|
705
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
706
|
+
if (publicKeyBytes.length !== 32) {
|
|
707
|
+
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if (signatureBytes.length !== 64) {
|
|
711
|
+
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
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;
|
|
743
|
+
}
|
|
519
744
|
}
|
|
520
745
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
];
|
|
533
|
-
var CLOUD_PROVIDERS2 = {
|
|
534
|
-
aws: ["54.", "52.", "35.", "18.", "3."],
|
|
535
|
-
gcp: ["35.", "34.", "104.", "107.", "108."],
|
|
536
|
-
azure: ["13.", "20.", "40.", "52.", "104."]
|
|
537
|
-
};
|
|
538
|
-
var EdgeAgentDetectorWithWasm = class {
|
|
539
|
-
constructor(enableWasm = true) {
|
|
540
|
-
this.enableWasm = enableWasm;
|
|
746
|
+
async function verifyAgentSignature(method, path, headers) {
|
|
747
|
+
const signature = headers["signature"] || headers["Signature"];
|
|
748
|
+
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
749
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
750
|
+
if (!signature || !signatureInput) {
|
|
751
|
+
return {
|
|
752
|
+
isValid: false,
|
|
753
|
+
confidence: 0,
|
|
754
|
+
reason: "No signature headers present",
|
|
755
|
+
verificationMethod: "none"
|
|
756
|
+
};
|
|
541
757
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
setWasmBaseUrl(url);
|
|
758
|
+
const parsed = parseSignatureInput(signatureInput);
|
|
759
|
+
if (!parsed) {
|
|
760
|
+
return {
|
|
761
|
+
isValid: false,
|
|
762
|
+
confidence: 0,
|
|
763
|
+
reason: "Invalid Signature-Input header",
|
|
764
|
+
verificationMethod: "none"
|
|
765
|
+
};
|
|
551
766
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
767
|
+
if (parsed.created) {
|
|
768
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
769
|
+
const age = now2 - parsed.created;
|
|
770
|
+
if (age > 300) {
|
|
771
|
+
return {
|
|
772
|
+
isValid: false,
|
|
773
|
+
confidence: 0,
|
|
774
|
+
reason: "Signature expired (older than 5 minutes)",
|
|
775
|
+
verificationMethod: "none"
|
|
776
|
+
};
|
|
559
777
|
}
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
778
|
+
if (age < -30) {
|
|
779
|
+
return {
|
|
780
|
+
isValid: false,
|
|
781
|
+
confidence: 0,
|
|
782
|
+
reason: "Signature timestamp is in the future",
|
|
783
|
+
verificationMethod: "none"
|
|
784
|
+
};
|
|
563
785
|
}
|
|
564
|
-
this.initPromise = (async () => {
|
|
565
|
-
try {
|
|
566
|
-
const wasmAvailable = await isWasmAvailable();
|
|
567
|
-
this.wasmEnabled = wasmAvailable;
|
|
568
|
-
if (this.wasmEnabled) {
|
|
569
|
-
console.log("[AgentShield] WASM detection enabled");
|
|
570
|
-
} else {
|
|
571
|
-
console.log("[AgentShield] WASM not available, using pattern detection");
|
|
572
|
-
}
|
|
573
|
-
} catch (error) {
|
|
574
|
-
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
575
|
-
this.wasmEnabled = false;
|
|
576
|
-
}
|
|
577
|
-
})();
|
|
578
|
-
await this.initPromise;
|
|
579
786
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
787
|
+
let agent;
|
|
788
|
+
let agentKey;
|
|
789
|
+
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
790
|
+
agent = "ChatGPT";
|
|
791
|
+
agentKey = "chatgpt";
|
|
792
|
+
}
|
|
793
|
+
if (!agent || !agentKey) {
|
|
794
|
+
return {
|
|
795
|
+
isValid: false,
|
|
796
|
+
confidence: 0,
|
|
797
|
+
reason: "Unknown signature agent",
|
|
798
|
+
verificationMethod: "none"
|
|
799
|
+
};
|
|
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
|
+
}
|
|
810
|
+
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
811
|
+
if (!key) {
|
|
812
|
+
return {
|
|
813
|
+
isValid: false,
|
|
814
|
+
confidence: 0,
|
|
815
|
+
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
816
|
+
verificationMethod: "none"
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
820
|
+
if (now < key.validFrom || now > key.validUntil) {
|
|
821
|
+
return {
|
|
822
|
+
isValid: false,
|
|
823
|
+
confidence: 0,
|
|
824
|
+
reason: "Key is not valid at current time",
|
|
825
|
+
verificationMethod: "none"
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
829
|
+
let signatureValue = signature;
|
|
830
|
+
if (signatureValue.startsWith("sig1=:")) {
|
|
831
|
+
signatureValue = signatureValue.substring(6);
|
|
832
|
+
}
|
|
833
|
+
if (signatureValue.endsWith(":")) {
|
|
834
|
+
signatureValue = signatureValue.slice(0, -1);
|
|
835
|
+
}
|
|
836
|
+
const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
|
|
837
|
+
if (isValid) {
|
|
838
|
+
return {
|
|
839
|
+
isValid: true,
|
|
840
|
+
agent,
|
|
841
|
+
keyid: parsed.keyid,
|
|
842
|
+
confidence: 1,
|
|
843
|
+
// 100% confidence for valid signature
|
|
844
|
+
verificationMethod: "signature"
|
|
845
|
+
};
|
|
846
|
+
} else {
|
|
847
|
+
return {
|
|
848
|
+
isValid: false,
|
|
849
|
+
confidence: 0,
|
|
850
|
+
reason: "Signature verification failed",
|
|
851
|
+
verificationMethod: "none"
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function hasSignatureHeaders(headers) {
|
|
856
|
+
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
857
|
+
}
|
|
858
|
+
function isChatGPTSignature(headers) {
|
|
859
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
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
|
+
}
|
|
874
|
+
}
|
|
875
|
+
var rules = loadRulesSync();
|
|
876
|
+
var EdgeAgentDetector = class {
|
|
877
|
+
rules;
|
|
878
|
+
constructor() {
|
|
879
|
+
this.rules = rules;
|
|
880
|
+
}
|
|
881
|
+
async analyze(input) {
|
|
584
882
|
const reasons = [];
|
|
585
883
|
let detectedAgent;
|
|
586
884
|
let verificationMethod;
|
|
@@ -590,144 +888,178 @@ var EdgeAgentDetectorWithWasm = class {
|
|
|
590
888
|
for (const [key, value] of Object.entries(headers)) {
|
|
591
889
|
normalizedHeaders[key.toLowerCase()] = value;
|
|
592
890
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
891
|
+
if (hasSignatureHeaders(headers)) {
|
|
892
|
+
try {
|
|
893
|
+
const signatureResult = await verifyAgentSignature(
|
|
894
|
+
input.method || "GET",
|
|
895
|
+
input.url || "/",
|
|
896
|
+
headers
|
|
897
|
+
);
|
|
898
|
+
if (signatureResult.isValid) {
|
|
899
|
+
confidence = signatureResult.confidence * 100;
|
|
900
|
+
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
901
|
+
if (signatureResult.agent) {
|
|
902
|
+
detectedAgent = {
|
|
903
|
+
type: signatureResult.agent.toLowerCase(),
|
|
904
|
+
name: signatureResult.agent
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
verificationMethod = signatureResult.verificationMethod;
|
|
908
|
+
if (signatureResult.keyid) {
|
|
909
|
+
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
910
|
+
}
|
|
911
|
+
} else {
|
|
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);
|
|
919
|
+
reasons.push("invalid_signature");
|
|
920
|
+
if (signatureResult.reason) {
|
|
921
|
+
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
922
|
+
}
|
|
923
|
+
if (isChatGPTSignature(headers)) {
|
|
924
|
+
reasons.push("claims_chatgpt");
|
|
925
|
+
detectedAgent = { type: "chatgpt", name: "ChatGPT (unverified)" };
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} catch (error) {
|
|
929
|
+
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
930
|
+
confidence = Math.max(confidence, 20);
|
|
931
|
+
reasons.push("signature_verification_error");
|
|
932
|
+
}
|
|
603
933
|
}
|
|
604
934
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
605
935
|
if (userAgent) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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}`);
|
|
617
956
|
if (!detectedAgent) {
|
|
618
|
-
detectedAgent = { type, name };
|
|
957
|
+
detectedAgent = { type: agentType, name: agentName };
|
|
619
958
|
verificationMethod = "pattern";
|
|
620
959
|
}
|
|
621
960
|
break;
|
|
622
961
|
}
|
|
623
962
|
}
|
|
624
963
|
}
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
"anthropic-client-id",
|
|
629
|
-
"x-goog-api-client",
|
|
630
|
-
"x-ms-copilot-id"
|
|
631
|
-
];
|
|
632
|
-
const foundAiHeaders = aiHeaders.filter(
|
|
633
|
-
(header) => normalizedHeaders[header]
|
|
964
|
+
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
965
|
+
const foundAiHeaders = suspiciousHeaders.filter(
|
|
966
|
+
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
634
967
|
);
|
|
635
968
|
if (foundAiHeaders.length > 0) {
|
|
636
|
-
|
|
969
|
+
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence * 100));
|
|
970
|
+
confidence = Math.max(confidence, maxConfidence);
|
|
637
971
|
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
638
972
|
}
|
|
639
973
|
const ip = input.ip || input.ipAddress;
|
|
640
974
|
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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);
|
|
644
993
|
reasons.push(`cloud_provider:${provider}`);
|
|
645
994
|
break;
|
|
646
995
|
}
|
|
647
996
|
}
|
|
648
997
|
}
|
|
649
|
-
if (reasons.length > 2) {
|
|
650
|
-
confidence = Math.min(confidence * 1.2,
|
|
998
|
+
if (reasons.length > 2 && confidence < 100) {
|
|
999
|
+
confidence = Math.min(confidence * 1.2, 95);
|
|
651
1000
|
}
|
|
1001
|
+
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
652
1002
|
return {
|
|
653
|
-
isAgent: confidence >
|
|
1003
|
+
isAgent: confidence > 30,
|
|
1004
|
+
// Updated to 0-100 scale (was 0.3)
|
|
654
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
|
|
655
1009
|
...detectedAgent && { detectedAgent },
|
|
656
1010
|
reasons,
|
|
657
|
-
...verificationMethod && {
|
|
658
|
-
|
|
1011
|
+
...verificationMethod && {
|
|
1012
|
+
verificationMethod
|
|
1013
|
+
},
|
|
1014
|
+
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 80 ? "medium" : "high",
|
|
1015
|
+
// Updated to 0-100 scale
|
|
659
1016
|
timestamp: /* @__PURE__ */ new Date()
|
|
660
1017
|
};
|
|
661
1018
|
}
|
|
662
1019
|
/**
|
|
663
|
-
*
|
|
1020
|
+
* Get agent type from rule key
|
|
664
1021
|
*/
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
...detectedAgent && { detectedAgent },
|
|
681
|
-
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
682
|
-
verificationMethod: wasmResult.verificationMethod,
|
|
683
|
-
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 0.9 ? "medium" : "high",
|
|
684
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
} catch (error) {
|
|
688
|
-
console.error("[AgentShield] WASM detection error:", error);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const patternResult = await this.patternDetection(input);
|
|
692
|
-
if (this.wasmEnabled && patternResult.confidence >= 0.85) {
|
|
693
|
-
patternResult.confidence = Math.min(0.95, patternResult.confidence + 0.1);
|
|
694
|
-
patternResult.reasons.push("wasm_enhanced");
|
|
695
|
-
if (!patternResult.verificationMethod) {
|
|
696
|
-
patternResult.verificationMethod = "wasm-enhanced";
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return patternResult;
|
|
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;
|
|
700
1037
|
}
|
|
701
1038
|
/**
|
|
702
|
-
*
|
|
1039
|
+
* Get agent name from rule key
|
|
703
1040
|
*/
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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;
|
|
718
1056
|
}
|
|
719
1057
|
};
|
|
720
|
-
var
|
|
1058
|
+
var EdgeAgentDetectorWrapper = class {
|
|
721
1059
|
detector;
|
|
722
1060
|
events = /* @__PURE__ */ new Map();
|
|
723
|
-
constructor(
|
|
724
|
-
this.detector = new
|
|
725
|
-
if (config?.baseUrl) {
|
|
726
|
-
this.detector.setBaseUrl(config.baseUrl);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
setBaseUrl(url) {
|
|
730
|
-
this.detector.setBaseUrl(url);
|
|
1061
|
+
constructor(_config) {
|
|
1062
|
+
this.detector = new EdgeAgentDetector();
|
|
731
1063
|
}
|
|
732
1064
|
async analyze(input) {
|
|
733
1065
|
const result = await this.detector.analyze(input);
|
|
@@ -748,10 +1080,13 @@ var EdgeAgentDetectorWrapperWithWasm = class {
|
|
|
748
1080
|
handlers.forEach((handler) => handler(...args));
|
|
749
1081
|
}
|
|
750
1082
|
async init() {
|
|
751
|
-
|
|
1083
|
+
return;
|
|
752
1084
|
}
|
|
753
1085
|
};
|
|
754
1086
|
|
|
1087
|
+
// src/middleware.ts
|
|
1088
|
+
init_edge_detector_with_wasm();
|
|
1089
|
+
|
|
755
1090
|
// src/session-tracker.ts
|
|
756
1091
|
var EdgeSessionTracker = class {
|
|
757
1092
|
config;
|
|
@@ -931,7 +1266,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
931
1266
|
}) : null;
|
|
932
1267
|
if (config.events) {
|
|
933
1268
|
Object.entries(config.events).forEach(([event, handler]) => {
|
|
934
|
-
|
|
1269
|
+
if (handler) {
|
|
1270
|
+
detector.on(event, handler);
|
|
1271
|
+
}
|
|
935
1272
|
});
|
|
936
1273
|
}
|
|
937
1274
|
const {
|
|
@@ -963,19 +1300,22 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
963
1300
|
const response2 = NextResponse.next();
|
|
964
1301
|
response2.headers.set("x-agentshield-detected", "true");
|
|
965
1302
|
response2.headers.set("x-agentshield-agent", existingSession.agent);
|
|
966
|
-
response2.headers.set(
|
|
967
|
-
"x-agentshield-confidence",
|
|
968
|
-
existingSession.confidence.toString()
|
|
969
|
-
);
|
|
1303
|
+
response2.headers.set("x-agentshield-confidence", existingSession.confidence.toString());
|
|
970
1304
|
response2.headers.set("x-agentshield-session", "continued");
|
|
971
1305
|
response2.headers.set("x-agentshield-session-id", existingSession.id);
|
|
972
1306
|
request.agentShield = {
|
|
973
1307
|
result: {
|
|
974
1308
|
isAgent: true,
|
|
975
1309
|
confidence: existingSession.confidence,
|
|
976
|
-
|
|
1310
|
+
detectionClass: { type: "AiAgent" },
|
|
1311
|
+
detectedAgent: {
|
|
1312
|
+
type: "ai_agent",
|
|
1313
|
+
name: existingSession.agent
|
|
1314
|
+
},
|
|
977
1315
|
timestamp: /* @__PURE__ */ new Date(),
|
|
978
|
-
verificationMethod: "
|
|
1316
|
+
verificationMethod: "behavioral",
|
|
1317
|
+
reasons: ["Session continued"],
|
|
1318
|
+
signals: []
|
|
979
1319
|
},
|
|
980
1320
|
session: existingSession,
|
|
981
1321
|
skipped: false
|
|
@@ -1005,7 +1345,7 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1005
1345
|
timestamp: /* @__PURE__ */ new Date()
|
|
1006
1346
|
};
|
|
1007
1347
|
const result = await detector.analyze(context);
|
|
1008
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1348
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1009
1349
|
if (onDetection) {
|
|
1010
1350
|
const customResponse = await onDetection(request, result);
|
|
1011
1351
|
if (customResponse) {
|
|
@@ -1024,11 +1364,9 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1024
1364
|
{ status: blockedResponse.status }
|
|
1025
1365
|
);
|
|
1026
1366
|
if (blockedResponse.headers) {
|
|
1027
|
-
Object.entries(blockedResponse.headers).forEach(
|
|
1028
|
-
(
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
);
|
|
1367
|
+
Object.entries(blockedResponse.headers).forEach(([key, value]) => {
|
|
1368
|
+
response2.headers.set(key, value);
|
|
1369
|
+
});
|
|
1032
1370
|
}
|
|
1033
1371
|
detector.emit("agent.blocked", result, context);
|
|
1034
1372
|
return response2;
|
|
@@ -1038,17 +1376,19 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1038
1376
|
case "rewrite":
|
|
1039
1377
|
return NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1040
1378
|
case "log":
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
+
}
|
|
1048
1388
|
break;
|
|
1049
1389
|
case "allow":
|
|
1050
1390
|
default:
|
|
1051
|
-
if (result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1391
|
+
if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1052
1392
|
detector.emit("agent.allowed", result, context);
|
|
1053
1393
|
}
|
|
1054
1394
|
break;
|
|
@@ -1060,14 +1400,11 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
1060
1400
|
};
|
|
1061
1401
|
let response = NextResponse.next();
|
|
1062
1402
|
response.headers.set("x-agentshield-detected", result.isAgent.toString());
|
|
1063
|
-
response.headers.set(
|
|
1064
|
-
"x-agentshield-confidence",
|
|
1065
|
-
result.confidence.toString()
|
|
1066
|
-
);
|
|
1403
|
+
response.headers.set("x-agentshield-confidence", result.confidence.toString());
|
|
1067
1404
|
if (result.detectedAgent?.name) {
|
|
1068
1405
|
response.headers.set("x-agentshield-agent", result.detectedAgent.name);
|
|
1069
1406
|
}
|
|
1070
|
-
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ??
|
|
1407
|
+
if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
|
|
1071
1408
|
response = await sessionTracker.track(request, response, result);
|
|
1072
1409
|
response.headers.set("x-agentshield-session", "new");
|
|
1073
1410
|
detector.emit("agent.session.started", result, context);
|
|
@@ -1085,7 +1422,7 @@ var middlewareInstance = null;
|
|
|
1085
1422
|
var isInitializing = false;
|
|
1086
1423
|
var initPromise2 = null;
|
|
1087
1424
|
function createAgentShieldMiddleware2(config) {
|
|
1088
|
-
return async function
|
|
1425
|
+
return async function agentShieldMiddleware2(request) {
|
|
1089
1426
|
if (!middlewareInstance) {
|
|
1090
1427
|
if (!isInitializing) {
|
|
1091
1428
|
isInitializing = true;
|
|
@@ -1102,48 +1439,81 @@ function createAgentShieldMiddleware2(config) {
|
|
|
1102
1439
|
};
|
|
1103
1440
|
}
|
|
1104
1441
|
|
|
1105
|
-
// src/
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1442
|
+
// src/edge-safe-detector.ts
|
|
1443
|
+
var AI_AGENT_PATTERNS = [
|
|
1444
|
+
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
1445
|
+
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
1446
|
+
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
1447
|
+
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
1448
|
+
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
1449
|
+
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
1450
|
+
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
1451
|
+
];
|
|
1452
|
+
var EdgeSafeDetector = class {
|
|
1453
|
+
async analyze(input) {
|
|
1454
|
+
const reasons = [];
|
|
1455
|
+
let detectedAgent;
|
|
1456
|
+
let confidence = 0;
|
|
1457
|
+
const headers = input.headers || {};
|
|
1458
|
+
const normalizedHeaders = {};
|
|
1459
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1460
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
1110
1461
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1462
|
+
const userAgent = input.userAgent || normalizedHeaders["user-agent"] || "";
|
|
1463
|
+
if (userAgent) {
|
|
1464
|
+
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
1465
|
+
if (pattern.test(userAgent)) {
|
|
1466
|
+
confidence = 85;
|
|
1467
|
+
reasons.push(`known_pattern:${type}`);
|
|
1468
|
+
detectedAgent = { type, name };
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1113
1472
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
(
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1473
|
+
const hasChrome = userAgent.toLowerCase().includes("chrome");
|
|
1474
|
+
const hasFirefox = userAgent.toLowerCase().includes("firefox");
|
|
1475
|
+
const hasSafari = userAgent.toLowerCase().includes("safari");
|
|
1476
|
+
const hasBrowserUA = hasChrome || hasFirefox || hasSafari;
|
|
1477
|
+
if (hasBrowserUA) {
|
|
1478
|
+
const hasSecChUa = !!normalizedHeaders["sec-ch-ua"];
|
|
1479
|
+
const hasSecFetch = !!normalizedHeaders["sec-fetch-site"];
|
|
1480
|
+
const hasAcceptLanguage = !!normalizedHeaders["accept-language"];
|
|
1481
|
+
const hasCookies = !!normalizedHeaders["cookie"];
|
|
1482
|
+
const missingHeaders = [];
|
|
1483
|
+
if (!hasSecChUa && hasChrome) missingHeaders.push("sec-ch-ua");
|
|
1484
|
+
if (!hasSecFetch) missingHeaders.push("sec-fetch");
|
|
1485
|
+
if (!hasAcceptLanguage) missingHeaders.push("accept-language");
|
|
1486
|
+
if (!hasCookies) missingHeaders.push("cookies");
|
|
1487
|
+
if (missingHeaders.length >= 2) {
|
|
1488
|
+
confidence = Math.max(confidence, 85);
|
|
1489
|
+
reasons.push("browser_ua_missing_headers");
|
|
1490
|
+
if (!detectedAgent && hasChrome && !hasSecChUa) {
|
|
1491
|
+
detectedAgent = { type: "perplexity", name: "Perplexity" };
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"];
|
|
1496
|
+
const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]);
|
|
1497
|
+
if (foundAiHeaders.length > 0) {
|
|
1498
|
+
confidence = Math.max(confidence, 60);
|
|
1499
|
+
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
isAgent: confidence > 30,
|
|
1503
|
+
// Updated to 0-100 scale (was 0.3)
|
|
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
|
|
1508
|
+
detectedAgent,
|
|
1509
|
+
reasons,
|
|
1510
|
+
verificationMethod: "pattern",
|
|
1511
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1512
|
+
confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low"
|
|
1513
|
+
// Updated to 0-100 scale
|
|
1514
|
+
};
|
|
1144
1515
|
}
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1516
|
+
};
|
|
1147
1517
|
|
|
1148
1518
|
// src/storage/memory-adapter.ts
|
|
1149
1519
|
var MemoryStorageAdapter = class {
|
|
@@ -1343,11 +1713,7 @@ var RedisStorageAdapter = class {
|
|
|
1343
1713
|
}
|
|
1344
1714
|
async cleanup(olderThan) {
|
|
1345
1715
|
const cutoff = olderThan.getTime();
|
|
1346
|
-
await this.redis.zremrangebyrank(
|
|
1347
|
-
this.timelineKey(),
|
|
1348
|
-
0,
|
|
1349
|
-
Math.floor(cutoff / 1e3)
|
|
1350
|
-
);
|
|
1716
|
+
await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3));
|
|
1351
1717
|
}
|
|
1352
1718
|
};
|
|
1353
1719
|
|
|
@@ -1368,7 +1734,10 @@ async function createStorageAdapter(config) {
|
|
|
1368
1734
|
});
|
|
1369
1735
|
return new RedisStorageAdapter(redis, config.ttl);
|
|
1370
1736
|
} catch (error) {
|
|
1371
|
-
console.warn(
|
|
1737
|
+
console.warn(
|
|
1738
|
+
"[AgentShield] Failed to initialize Redis storage, falling back to memory:",
|
|
1739
|
+
error
|
|
1740
|
+
);
|
|
1372
1741
|
return new MemoryStorageAdapter();
|
|
1373
1742
|
}
|
|
1374
1743
|
}
|
|
@@ -1418,39 +1787,60 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1418
1787
|
};
|
|
1419
1788
|
let detector = null;
|
|
1420
1789
|
let detectorInitPromise = null;
|
|
1790
|
+
let wasmConfidenceUtils = null;
|
|
1421
1791
|
const getDetector = async (requestUrl) => {
|
|
1422
1792
|
if (detector) {
|
|
1423
|
-
if (requestUrl && "setBaseUrl" in detector) {
|
|
1424
|
-
detector.setBaseUrl(requestUrl);
|
|
1425
|
-
}
|
|
1426
1793
|
return detector;
|
|
1427
1794
|
}
|
|
1428
1795
|
if (detectorInitPromise) {
|
|
1429
1796
|
await detectorInitPromise;
|
|
1430
|
-
if (requestUrl && detector && "setBaseUrl" in detector) {
|
|
1431
|
-
detector.setBaseUrl(requestUrl);
|
|
1432
|
-
}
|
|
1433
1797
|
return detector;
|
|
1434
1798
|
}
|
|
1435
1799
|
detectorInitPromise = (async () => {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
if (
|
|
1439
|
-
console.
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1800
|
+
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge";
|
|
1801
|
+
if (isEdgeRuntime) {
|
|
1802
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1803
|
+
console.debug("[AgentShield] Edge Runtime detected - using pattern detection");
|
|
1804
|
+
}
|
|
1805
|
+
detector = new EdgeSafeDetector();
|
|
1806
|
+
} else {
|
|
1807
|
+
try {
|
|
1808
|
+
try {
|
|
1809
|
+
const wasmUtils = await Promise.resolve().then(() => (init_wasm_confidence(), wasm_confidence_exports));
|
|
1810
|
+
wasmConfidenceUtils = wasmUtils;
|
|
1811
|
+
const wasmAvailable = await wasmUtils.checkWasmAvailability();
|
|
1812
|
+
if (wasmAvailable) {
|
|
1813
|
+
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1814
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1815
|
+
console.debug(
|
|
1816
|
+
"[AgentShield] \u2705 WASM support detected - enhanced detection enabled"
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1820
|
+
if (requestUrl && "setBaseUrl" in detector) {
|
|
1821
|
+
detector.setBaseUrl(requestUrl);
|
|
1822
|
+
}
|
|
1823
|
+
} else {
|
|
1824
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1825
|
+
console.debug(
|
|
1826
|
+
"[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)"
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
detector = new EdgeSafeDetector();
|
|
1830
|
+
}
|
|
1831
|
+
} catch (wasmError) {
|
|
1832
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1833
|
+
console.debug("[AgentShield] WASM utilities not available, using pattern detection");
|
|
1834
|
+
}
|
|
1835
|
+
detector = new EdgeSafeDetector();
|
|
1836
|
+
}
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
console.warn("[AgentShield] Failed to initialize enhanced detector, using fallback");
|
|
1839
|
+
detector = new EdgeSafeDetector();
|
|
1444
1840
|
}
|
|
1445
|
-
} catch (error) {
|
|
1446
|
-
console.warn("[AgentShield] Failed to initialize WASM, using fallback:", error);
|
|
1447
|
-
detector = new EdgeAgentDetectorWrapper({});
|
|
1448
1841
|
}
|
|
1449
1842
|
})();
|
|
1450
1843
|
await detectorInitPromise;
|
|
1451
|
-
if (requestUrl && detector && "setBaseUrl" in detector) {
|
|
1452
|
-
detector.setBaseUrl(requestUrl);
|
|
1453
|
-
}
|
|
1454
1844
|
return detector;
|
|
1455
1845
|
};
|
|
1456
1846
|
const sessionManager = new SessionManager();
|
|
@@ -1476,17 +1866,20 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1476
1866
|
const result = await activeDetector.analyze(context);
|
|
1477
1867
|
let finalConfidence = result.confidence;
|
|
1478
1868
|
let verificationMethod = result.verificationMethod || "pattern";
|
|
1479
|
-
if (result.isAgent) {
|
|
1869
|
+
if (result.isAgent && wasmConfidenceUtils) {
|
|
1480
1870
|
const reasons = result.reasons || [];
|
|
1481
|
-
if (shouldIndicateWasmVerification(result.confidence)) {
|
|
1482
|
-
finalConfidence = getWasmConfidenceBoost(result.confidence, reasons);
|
|
1483
|
-
verificationMethod = getVerificationMethod(finalConfidence, reasons);
|
|
1871
|
+
if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) {
|
|
1872
|
+
finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons);
|
|
1873
|
+
verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons);
|
|
1484
1874
|
}
|
|
1485
1875
|
}
|
|
1486
1876
|
if (result.isAgent && finalConfidence >= (config.confidenceThreshold ?? 0.7)) {
|
|
1487
1877
|
if (sessionTrackingEnabled) {
|
|
1488
1878
|
const storage = await getStorage();
|
|
1489
|
-
const sessionId = sessionManager.generateSessionId(
|
|
1879
|
+
const sessionId = sessionManager.generateSessionId(
|
|
1880
|
+
ipAddress || void 0,
|
|
1881
|
+
userAgent || void 0
|
|
1882
|
+
);
|
|
1490
1883
|
const event = {
|
|
1491
1884
|
eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
1492
1885
|
sessionId,
|
|
@@ -1552,15 +1945,18 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1552
1945
|
{ status }
|
|
1553
1946
|
);
|
|
1554
1947
|
response2.headers.set("x-agentshield-detected", "true");
|
|
1555
|
-
response2.headers.set(
|
|
1948
|
+
response2.headers.set(
|
|
1949
|
+
"x-agentshield-confidence",
|
|
1950
|
+
String(Math.round(finalConfidence * 100))
|
|
1951
|
+
);
|
|
1556
1952
|
response2.headers.set("x-agentshield-agent", result.detectedAgent?.name || "Unknown");
|
|
1557
1953
|
response2.headers.set("x-agentshield-verification", verificationMethod);
|
|
1558
1954
|
return response2;
|
|
1559
1955
|
}
|
|
1560
|
-
case "log":
|
|
1956
|
+
case "log": {
|
|
1561
1957
|
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
1562
|
-
if (isInteresting) {
|
|
1563
|
-
console.
|
|
1958
|
+
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
1959
|
+
console.debug(`[AgentShield] \u{1F916} AI Agent detected (${verificationMethod}):`, {
|
|
1564
1960
|
agent: result.detectedAgent?.name,
|
|
1565
1961
|
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
1566
1962
|
path: pathWithQuery,
|
|
@@ -1568,6 +1964,7 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1568
1964
|
});
|
|
1569
1965
|
}
|
|
1570
1966
|
break;
|
|
1967
|
+
}
|
|
1571
1968
|
}
|
|
1572
1969
|
}
|
|
1573
1970
|
const response = NextResponse.next();
|
|
@@ -1586,6 +1983,294 @@ function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
|
1586
1983
|
};
|
|
1587
1984
|
}
|
|
1588
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
|
+
|
|
1589
2274
|
// src/index.ts
|
|
1590
2275
|
var VERSION = "0.1.0";
|
|
1591
2276
|
/**
|
|
@@ -1594,6 +2279,6 @@ var VERSION = "0.1.0";
|
|
|
1594
2279
|
* @license MIT OR Apache-2.0
|
|
1595
2280
|
*/
|
|
1596
2281
|
|
|
1597
|
-
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 };
|
|
1598
2283
|
//# sourceMappingURL=index.mjs.map
|
|
1599
2284
|
//# sourceMappingURL=index.mjs.map
|