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