@kya-os/agentshield-nextjs 0.3.3 → 0.3.5
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/README.md +21 -369
- package/index.js +9 -0
- package/package.json +7 -141
- package/EDGE_RUNTIME_WASM_SETUP.md +0 -348
- package/bin/setup-edge-wasm.js +0 -525
- package/dist/.tsbuildinfo +0 -1
- package/dist/api-client.d.mts +0 -196
- package/dist/api-client.d.ts +0 -196
- package/dist/api-client.js +0 -200
- package/dist/api-client.js.map +0 -1
- package/dist/api-client.mjs +0 -196
- package/dist/api-client.mjs.map +0 -1
- package/dist/api-middleware.d.mts +0 -140
- package/dist/api-middleware.d.ts +0 -140
- package/dist/api-middleware.js +0 -511
- package/dist/api-middleware.js.map +0 -1
- package/dist/api-middleware.mjs +0 -508
- package/dist/api-middleware.mjs.map +0 -1
- package/dist/create-middleware.d.mts +0 -17
- package/dist/create-middleware.d.ts +0 -17
- package/dist/create-middleware.js +0 -1381
- package/dist/create-middleware.js.map +0 -1
- package/dist/create-middleware.mjs +0 -1358
- package/dist/create-middleware.mjs.map +0 -1
- package/dist/edge/index.d.mts +0 -110
- package/dist/edge/index.d.ts +0 -110
- package/dist/edge/index.js +0 -277
- package/dist/edge/index.js.map +0 -1
- package/dist/edge/index.mjs +0 -275
- package/dist/edge/index.mjs.map +0 -1
- package/dist/edge-detector-wrapper.d.mts +0 -34
- package/dist/edge-detector-wrapper.d.ts +0 -34
- package/dist/edge-detector-wrapper.js +0 -596
- package/dist/edge-detector-wrapper.js.map +0 -1
- package/dist/edge-detector-wrapper.mjs +0 -574
- package/dist/edge-detector-wrapper.mjs.map +0 -1
- package/dist/edge-runtime-loader.d.mts +0 -50
- package/dist/edge-runtime-loader.d.ts +0 -50
- package/dist/edge-runtime-loader.js +0 -204
- package/dist/edge-runtime-loader.js.map +0 -1
- package/dist/edge-runtime-loader.mjs +0 -201
- package/dist/edge-runtime-loader.mjs.map +0 -1
- package/dist/edge-wasm-middleware.d.mts +0 -68
- package/dist/edge-wasm-middleware.d.ts +0 -68
- package/dist/edge-wasm-middleware.js +0 -318
- package/dist/edge-wasm-middleware.js.map +0 -1
- package/dist/edge-wasm-middleware.mjs +0 -315
- package/dist/edge-wasm-middleware.mjs.map +0 -1
- package/dist/enhanced-middleware.d.mts +0 -153
- package/dist/enhanced-middleware.d.ts +0 -153
- package/dist/enhanced-middleware.js +0 -1082
- package/dist/enhanced-middleware.js.map +0 -1
- package/dist/enhanced-middleware.mjs +0 -1080
- package/dist/enhanced-middleware.mjs.map +0 -1
- package/dist/index.d.mts +0 -24
- package/dist/index.d.ts +0 -24
- package/dist/index.js +0 -2717
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2662
- package/dist/index.mjs.map +0 -1
- package/dist/middleware.d.mts +0 -21
- package/dist/middleware.d.ts +0 -21
- package/dist/middleware.js +0 -1362
- package/dist/middleware.js.map +0 -1
- package/dist/middleware.mjs +0 -1339
- package/dist/middleware.mjs.map +0 -1
- package/dist/nodejs-wasm-loader.d.mts +0 -25
- package/dist/nodejs-wasm-loader.d.ts +0 -25
- package/dist/nodejs-wasm-loader.js +0 -78
- package/dist/nodejs-wasm-loader.js.map +0 -1
- package/dist/nodejs-wasm-loader.mjs +0 -68
- package/dist/nodejs-wasm-loader.mjs.map +0 -1
- package/dist/policy.d.mts +0 -162
- package/dist/policy.d.ts +0 -162
- package/dist/policy.js +0 -189
- package/dist/policy.js.map +0 -1
- package/dist/policy.mjs +0 -165
- package/dist/policy.mjs.map +0 -1
- package/dist/session-tracker.d.mts +0 -55
- package/dist/session-tracker.d.ts +0 -55
- package/dist/session-tracker.js +0 -170
- package/dist/session-tracker.js.map +0 -1
- package/dist/session-tracker.mjs +0 -167
- package/dist/session-tracker.mjs.map +0 -1
- package/dist/signature-verifier.d.mts +0 -33
- package/dist/signature-verifier.d.ts +0 -33
- package/dist/signature-verifier.js +0 -386
- package/dist/signature-verifier.js.map +0 -1
- package/dist/signature-verifier.mjs +0 -362
- package/dist/signature-verifier.mjs.map +0 -1
- package/dist/types-DVmy9NE3.d.mts +0 -105
- package/dist/types-DVmy9NE3.d.ts +0 -105
- package/dist/wasm-middleware.d.mts +0 -63
- package/dist/wasm-middleware.d.ts +0 -63
- package/dist/wasm-middleware.js +0 -98
- package/dist/wasm-middleware.js.map +0 -1
- package/dist/wasm-middleware.mjs +0 -95
- package/dist/wasm-middleware.mjs.map +0 -1
- package/dist/wasm-setup.d.mts +0 -46
- package/dist/wasm-setup.d.ts +0 -46
- package/dist/wasm-setup.js +0 -157
- package/dist/wasm-setup.js.map +0 -1
- package/dist/wasm-setup.mjs +0 -148
- package/dist/wasm-setup.mjs.map +0 -1
- package/templates/middleware-wasm-100.ts +0 -151
- package/wasm/agentshield_wasm.d.ts +0 -479
- package/wasm/agentshield_wasm.js +0 -1536
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/wasm/package.json +0 -30
- package/wasm.d.ts +0 -21
package/dist/index.js
DELETED
|
@@ -1,2717 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var agentshieldShared = require('@kya-os/agentshield-shared');
|
|
4
|
-
var server = require('next/server');
|
|
5
|
-
var ed25519 = require('@noble/ed25519');
|
|
6
|
-
var sha2_js = require('@noble/hashes/sha2.js');
|
|
7
|
-
|
|
8
|
-
function _interopNamespace(e) {
|
|
9
|
-
if (e && e.__esModule) return e;
|
|
10
|
-
var n = Object.create(null);
|
|
11
|
-
if (e) {
|
|
12
|
-
Object.keys(e).forEach(function (k) {
|
|
13
|
-
if (k !== 'default') {
|
|
14
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
get: function () { return e[k]; }
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
n.default = e;
|
|
23
|
-
return Object.freeze(n);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
var ed25519__namespace = /*#__PURE__*/_interopNamespace(ed25519);
|
|
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;
|
|
32
|
-
};
|
|
33
|
-
var __export = (target, all) => {
|
|
34
|
-
for (var name in all)
|
|
35
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// src/wasm-loader.ts
|
|
39
|
-
function setWasmBaseUrl(url) {
|
|
40
|
-
baseUrl = url;
|
|
41
|
-
}
|
|
42
|
-
function getWasmUrl() {
|
|
43
|
-
if (baseUrl) {
|
|
44
|
-
try {
|
|
45
|
-
const url = new URL(baseUrl);
|
|
46
|
-
return `${url.origin}${WASM_PATH}`;
|
|
47
|
-
} catch {
|
|
48
|
-
return WASM_PATH;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return WASM_PATH;
|
|
52
|
-
}
|
|
53
|
-
async function initWasm() {
|
|
54
|
-
if (wasmExports) return true;
|
|
55
|
-
if (initPromise) {
|
|
56
|
-
await initPromise;
|
|
57
|
-
return !!wasmExports;
|
|
58
|
-
}
|
|
59
|
-
initPromise = (async () => {
|
|
60
|
-
try {
|
|
61
|
-
const controller = new AbortController();
|
|
62
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
63
|
-
try {
|
|
64
|
-
const wasmUrl = getWasmUrl();
|
|
65
|
-
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
66
|
-
try {
|
|
67
|
-
const response2 = await fetch(wasmUrl, { signal: controller.signal });
|
|
68
|
-
clearTimeout(timeout);
|
|
69
|
-
if (!response2.ok) {
|
|
70
|
-
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
71
|
-
}
|
|
72
|
-
const streamResponse = response2.clone();
|
|
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);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
__wbindgen_throw: (ptr, len) => {
|
|
81
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
wasmInstance = instance;
|
|
86
|
-
wasmExports = instance.exports;
|
|
87
|
-
if (process.env.NODE_ENV !== "production") {
|
|
88
|
-
console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
89
|
-
}
|
|
90
|
-
return;
|
|
91
|
-
} catch (streamError) {
|
|
92
|
-
if (!controller.signal.aborted) {
|
|
93
|
-
if (process.env.NODE_ENV !== "production") {
|
|
94
|
-
console.debug(
|
|
95
|
-
"[AgentShield] Streaming compilation failed, falling back to standard compilation"
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
throw streamError;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const response = await fetch(wasmUrl, { signal: controller.signal });
|
|
104
|
-
clearTimeout(timeout);
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
throw new Error(`Failed to fetch WASM: ${response.status}`);
|
|
107
|
-
}
|
|
108
|
-
const wasmArrayBuffer = await response.arrayBuffer();
|
|
109
|
-
const compiledModule = await WebAssembly.compile(wasmArrayBuffer);
|
|
110
|
-
const imports = {
|
|
111
|
-
wbg: {
|
|
112
|
-
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
113
|
-
if (process.env.NODE_ENV !== "production") {
|
|
114
|
-
console.debug("WASM:", ptr, len);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
__wbindgen_throw: (ptr, len) => {
|
|
118
|
-
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
|
|
123
|
-
wasmExports = wasmInstance.exports;
|
|
124
|
-
if (process.env.NODE_ENV !== "production") {
|
|
125
|
-
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
126
|
-
}
|
|
127
|
-
} catch (fetchError) {
|
|
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
|
-
);
|
|
133
|
-
} else {
|
|
134
|
-
console.warn(
|
|
135
|
-
"[AgentShield] Failed to fetch WASM file:",
|
|
136
|
-
error.message || "Unknown error"
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
wasmExports = null;
|
|
140
|
-
}
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
143
|
-
wasmExports = null;
|
|
144
|
-
}
|
|
145
|
-
})();
|
|
146
|
-
await initPromise;
|
|
147
|
-
return !!wasmExports;
|
|
148
|
-
}
|
|
149
|
-
async function detectAgentWithWasm(_userAgent, _headers, _ipAddress) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
async function getWasmVersion() {
|
|
153
|
-
const initialized = await initWasm();
|
|
154
|
-
if (!initialized || !wasmExports) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
if (typeof wasmExports.version === "function") {
|
|
158
|
-
return wasmExports.version();
|
|
159
|
-
}
|
|
160
|
-
return "unknown";
|
|
161
|
-
}
|
|
162
|
-
async function isWasmAvailable() {
|
|
163
|
-
try {
|
|
164
|
-
const initialized = await initWasm();
|
|
165
|
-
if (!initialized) return false;
|
|
166
|
-
const version = await getWasmVersion();
|
|
167
|
-
return version !== null;
|
|
168
|
-
} catch {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
var wasmInstance, wasmExports, initPromise, WASM_PATH, baseUrl;
|
|
173
|
-
var init_wasm_loader = __esm({
|
|
174
|
-
"src/wasm-loader.ts"() {
|
|
175
|
-
wasmInstance = null;
|
|
176
|
-
wasmExports = null;
|
|
177
|
-
initPromise = null;
|
|
178
|
-
WASM_PATH = "/wasm/agentshield_wasm_bg.wasm";
|
|
179
|
-
baseUrl = null;
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// src/edge-detector-with-wasm.ts
|
|
184
|
-
var edge_detector_with_wasm_exports = {};
|
|
185
|
-
__export(edge_detector_with_wasm_exports, {
|
|
186
|
-
EdgeAgentDetectorWithWasm: () => EdgeAgentDetectorWithWasm,
|
|
187
|
-
EdgeAgentDetectorWrapperWithWasm: () => EdgeAgentDetectorWrapperWithWasm
|
|
188
|
-
});
|
|
189
|
-
var rules2, EdgeAgentDetectorWithWasm, EdgeAgentDetectorWrapperWithWasm;
|
|
190
|
-
var init_edge_detector_with_wasm = __esm({
|
|
191
|
-
"src/edge-detector-with-wasm.ts"() {
|
|
192
|
-
init_wasm_loader();
|
|
193
|
-
rules2 = agentshieldShared.loadRulesSync();
|
|
194
|
-
EdgeAgentDetectorWithWasm = class {
|
|
195
|
-
constructor(enableWasm = true) {
|
|
196
|
-
this.enableWasm = enableWasm;
|
|
197
|
-
this.rules = rules2;
|
|
198
|
-
}
|
|
199
|
-
wasmEnabled = false;
|
|
200
|
-
initPromise = null;
|
|
201
|
-
baseUrl = null;
|
|
202
|
-
rules;
|
|
203
|
-
/**
|
|
204
|
-
* Set the base URL for WASM loading in Edge Runtime
|
|
205
|
-
*/
|
|
206
|
-
setBaseUrl(url) {
|
|
207
|
-
this.baseUrl = url;
|
|
208
|
-
setWasmBaseUrl(url);
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Initialize the detector (including WASM if enabled)
|
|
212
|
-
*/
|
|
213
|
-
async init() {
|
|
214
|
-
if (!this.enableWasm) {
|
|
215
|
-
this.wasmEnabled = false;
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
if (this.initPromise) {
|
|
219
|
-
await this.initPromise;
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
this.initPromise = (async () => {
|
|
223
|
-
try {
|
|
224
|
-
const wasmAvailable = await isWasmAvailable();
|
|
225
|
-
if (wasmAvailable) {
|
|
226
|
-
if (this.baseUrl) {
|
|
227
|
-
setWasmBaseUrl(this.baseUrl);
|
|
228
|
-
}
|
|
229
|
-
await initWasm();
|
|
230
|
-
this.wasmEnabled = true;
|
|
231
|
-
} else {
|
|
232
|
-
this.wasmEnabled = false;
|
|
233
|
-
}
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.error("[AgentShield] Failed to initialize WASM:", error);
|
|
236
|
-
this.wasmEnabled = false;
|
|
237
|
-
}
|
|
238
|
-
})();
|
|
239
|
-
await this.initPromise;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Pattern-based detection (fallback)
|
|
243
|
-
*/
|
|
244
|
-
async patternDetection(input) {
|
|
245
|
-
const reasons = [];
|
|
246
|
-
let detectedAgent;
|
|
247
|
-
let verificationMethod;
|
|
248
|
-
let confidence = 0;
|
|
249
|
-
const headers = input.headers || {};
|
|
250
|
-
const normalizedHeaders = {};
|
|
251
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
252
|
-
normalizedHeaders[key.toLowerCase()] = value;
|
|
253
|
-
}
|
|
254
|
-
const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
|
|
255
|
-
const signatureAgent = normalizedHeaders["signature-agent"];
|
|
256
|
-
const isChatGPT = (() => {
|
|
257
|
-
try {
|
|
258
|
-
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
259
|
-
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
260
|
-
} catch {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
})();
|
|
264
|
-
if (isChatGPT) {
|
|
265
|
-
confidence = 85;
|
|
266
|
-
reasons.push("signature_agent:chatgpt");
|
|
267
|
-
detectedAgent = { type: "chatgpt", name: "ChatGPT" };
|
|
268
|
-
verificationMethod = "signature";
|
|
269
|
-
} else if (signaturePresent) {
|
|
270
|
-
confidence = Math.max(confidence, 40);
|
|
271
|
-
reasons.push("signature_present");
|
|
272
|
-
}
|
|
273
|
-
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
274
|
-
if (userAgent) {
|
|
275
|
-
for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
|
|
276
|
-
const matched = agentRule.patterns.some((pattern) => {
|
|
277
|
-
const regex = new RegExp(pattern, "i");
|
|
278
|
-
return regex.test(userAgent);
|
|
279
|
-
});
|
|
280
|
-
if (matched) {
|
|
281
|
-
const agentType = this.getAgentType(agentKey);
|
|
282
|
-
const agentName = this.getAgentName(agentKey);
|
|
283
|
-
confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
|
|
284
|
-
reasons.push(`known_pattern:${agentType}`);
|
|
285
|
-
if (!detectedAgent) {
|
|
286
|
-
detectedAgent = { type: agentType, name: agentName };
|
|
287
|
-
verificationMethod = "pattern";
|
|
288
|
-
}
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
294
|
-
const foundAiHeaders = suspiciousHeaders.filter(
|
|
295
|
-
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
296
|
-
);
|
|
297
|
-
if (foundAiHeaders.length > 0) {
|
|
298
|
-
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
|
|
299
|
-
confidence = Math.max(confidence, maxConfidence);
|
|
300
|
-
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
301
|
-
}
|
|
302
|
-
const ip = input.ip || input.ipAddress;
|
|
303
|
-
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
304
|
-
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
305
|
-
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
306
|
-
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
307
|
-
continue;
|
|
308
|
-
const matched = ipRule.ranges.some((range) => {
|
|
309
|
-
const prefix = range.split("/")[0];
|
|
310
|
-
const prefixParts = prefix.split(".");
|
|
311
|
-
const ipParts = ip.split(".");
|
|
312
|
-
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
313
|
-
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return true;
|
|
318
|
-
});
|
|
319
|
-
if (matched) {
|
|
320
|
-
confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
|
|
321
|
-
reasons.push(`cloud_provider:${provider}`);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (reasons.length > 2) {
|
|
327
|
-
confidence = Math.min(Math.round(confidence * 1.2), 95);
|
|
328
|
-
}
|
|
329
|
-
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
330
|
-
return {
|
|
331
|
-
isAgent: confidence > 30,
|
|
332
|
-
// 30% threshold
|
|
333
|
-
confidence,
|
|
334
|
-
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
335
|
-
signals: [],
|
|
336
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
337
|
-
...detectedAgent && { detectedAgent },
|
|
338
|
-
reasons,
|
|
339
|
-
...verificationMethod && {
|
|
340
|
-
verificationMethod: agentshieldShared.mapVerificationMethod(verificationMethod)
|
|
341
|
-
},
|
|
342
|
-
forgeabilityRisk: confidence > 80 ? "medium" : "high",
|
|
343
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Analyze request with WASM enhancement when available
|
|
348
|
-
*/
|
|
349
|
-
async analyze(input) {
|
|
350
|
-
await this.init();
|
|
351
|
-
if (this.wasmEnabled) {
|
|
352
|
-
try {
|
|
353
|
-
const wasmResult = await detectAgentWithWasm(
|
|
354
|
-
input.userAgent || input.headers?.["user-agent"],
|
|
355
|
-
input.headers || {},
|
|
356
|
-
input.ip || input.ipAddress
|
|
357
|
-
);
|
|
358
|
-
if (wasmResult) {
|
|
359
|
-
const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
|
|
360
|
-
return {
|
|
361
|
-
isAgent: wasmResult.isAgent,
|
|
362
|
-
confidence: wasmResult.confidence,
|
|
363
|
-
detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
|
|
364
|
-
signals: [],
|
|
365
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
366
|
-
...detectedAgent && { detectedAgent },
|
|
367
|
-
reasons: [`wasm:${wasmResult.verificationMethod}`],
|
|
368
|
-
verificationMethod: agentshieldShared.mapVerificationMethod(wasmResult.verificationMethod),
|
|
369
|
-
forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
|
|
370
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
} catch (error) {
|
|
374
|
-
console.error("[AgentShield] WASM detection error:", error);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const patternResult = await this.patternDetection(input);
|
|
378
|
-
if (this.wasmEnabled && patternResult.confidence >= 85) {
|
|
379
|
-
patternResult.confidence = Math.min(95, patternResult.confidence + 10);
|
|
380
|
-
patternResult.reasons.push("wasm_enhanced");
|
|
381
|
-
}
|
|
382
|
-
return patternResult;
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Get agent type from rule key
|
|
386
|
-
*/
|
|
387
|
-
getAgentType(agentKey) {
|
|
388
|
-
const typeMap = {
|
|
389
|
-
openai_gptbot: "openai",
|
|
390
|
-
anthropic_claude: "anthropic",
|
|
391
|
-
perplexity_bot: "perplexity",
|
|
392
|
-
google_ai: "google",
|
|
393
|
-
microsoft_ai: "microsoft",
|
|
394
|
-
meta_ai: "meta",
|
|
395
|
-
cohere_bot: "cohere",
|
|
396
|
-
huggingface_bot: "huggingface",
|
|
397
|
-
generic_bot: "generic",
|
|
398
|
-
dev_tools: "dev",
|
|
399
|
-
automation_tools: "automation"
|
|
400
|
-
};
|
|
401
|
-
return typeMap[agentKey] || agentKey;
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Get agent name from rule key
|
|
405
|
-
*/
|
|
406
|
-
getAgentName(agentKey) {
|
|
407
|
-
const nameMap = {
|
|
408
|
-
openai_gptbot: "ChatGPT/GPTBot",
|
|
409
|
-
anthropic_claude: "Claude",
|
|
410
|
-
perplexity_bot: "Perplexity",
|
|
411
|
-
google_ai: "Google AI",
|
|
412
|
-
microsoft_ai: "Microsoft Copilot",
|
|
413
|
-
meta_ai: "Meta AI",
|
|
414
|
-
cohere_bot: "Cohere",
|
|
415
|
-
huggingface_bot: "HuggingFace",
|
|
416
|
-
generic_bot: "Generic Bot",
|
|
417
|
-
dev_tools: "Development Tool",
|
|
418
|
-
automation_tools: "Automation Tool"
|
|
419
|
-
};
|
|
420
|
-
return nameMap[agentKey] || agentKey;
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Map agent names from WASM to consistent format
|
|
424
|
-
*/
|
|
425
|
-
mapAgentName(agent) {
|
|
426
|
-
const lowerAgent = agent.toLowerCase();
|
|
427
|
-
if (lowerAgent.includes("chatgpt")) {
|
|
428
|
-
return { type: "chatgpt", name: "ChatGPT" };
|
|
429
|
-
} else if (lowerAgent.includes("claude")) {
|
|
430
|
-
return { type: "claude", name: "Claude" };
|
|
431
|
-
} else if (lowerAgent.includes("perplexity")) {
|
|
432
|
-
return { type: "perplexity", name: "Perplexity" };
|
|
433
|
-
} else if (lowerAgent.includes("bing")) {
|
|
434
|
-
return { type: "bing", name: "Bing AI" };
|
|
435
|
-
} else if (lowerAgent.includes("anthropic")) {
|
|
436
|
-
return { type: "anthropic", name: "Anthropic" };
|
|
437
|
-
}
|
|
438
|
-
return { type: "unknown", name: agent };
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
EdgeAgentDetectorWrapperWithWasm = class {
|
|
442
|
-
detector;
|
|
443
|
-
events = /* @__PURE__ */ new Map();
|
|
444
|
-
constructor(config) {
|
|
445
|
-
this.detector = new EdgeAgentDetectorWithWasm(config?.enableWasm ?? true);
|
|
446
|
-
if (config?.baseUrl) {
|
|
447
|
-
this.detector.setBaseUrl(config.baseUrl);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
setBaseUrl(url) {
|
|
451
|
-
this.detector.setBaseUrl(url);
|
|
452
|
-
}
|
|
453
|
-
async analyze(input) {
|
|
454
|
-
const result = await this.detector.analyze(input);
|
|
455
|
-
if (result.isAgent && this.events.has("agent.detected")) {
|
|
456
|
-
const handlers = this.events.get("agent.detected") || [];
|
|
457
|
-
handlers.forEach((handler) => handler(result, input));
|
|
458
|
-
}
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
on(event, handler) {
|
|
462
|
-
if (!this.events.has(event)) {
|
|
463
|
-
this.events.set(event, []);
|
|
464
|
-
}
|
|
465
|
-
this.events.get(event).push(handler);
|
|
466
|
-
}
|
|
467
|
-
emit(event, ...args) {
|
|
468
|
-
const handlers = this.events.get(event) || [];
|
|
469
|
-
handlers.forEach((handler) => handler(...args));
|
|
470
|
-
}
|
|
471
|
-
async init() {
|
|
472
|
-
await this.detector.init();
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// src/wasm-confidence.ts
|
|
479
|
-
var wasm_confidence_exports = {};
|
|
480
|
-
__export(wasm_confidence_exports, {
|
|
481
|
-
checkWasmAvailability: () => checkWasmAvailability,
|
|
482
|
-
getVerificationMethod: () => getVerificationMethod,
|
|
483
|
-
getWasmConfidenceBoost: () => getWasmConfidenceBoost,
|
|
484
|
-
shouldIndicateWasmVerification: () => shouldIndicateWasmVerification
|
|
485
|
-
});
|
|
486
|
-
async function checkWasmAvailability() {
|
|
487
|
-
try {
|
|
488
|
-
if (typeof WebAssembly === "undefined") {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
if (!WebAssembly.instantiate || !WebAssembly.Module) {
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
|
-
return true;
|
|
495
|
-
} catch {
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
function shouldIndicateWasmVerification(confidence) {
|
|
500
|
-
return confidence >= 85 && confidence < 100;
|
|
501
|
-
}
|
|
502
|
-
function getWasmConfidenceBoost(baseConfidence, reasons = []) {
|
|
503
|
-
if (reasons.some(
|
|
504
|
-
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
505
|
-
)) {
|
|
506
|
-
return 100;
|
|
507
|
-
}
|
|
508
|
-
if (baseConfidence >= 85) {
|
|
509
|
-
return 95;
|
|
510
|
-
}
|
|
511
|
-
if (baseConfidence >= 70) {
|
|
512
|
-
return Math.min(baseConfidence * 1.1, 90);
|
|
513
|
-
}
|
|
514
|
-
return baseConfidence;
|
|
515
|
-
}
|
|
516
|
-
function getVerificationMethod(confidence, reasons = []) {
|
|
517
|
-
if (reasons.some(
|
|
518
|
-
(r) => r.includes("signature_agent") && !r.includes("signature_headers_present")
|
|
519
|
-
)) {
|
|
520
|
-
return "signature";
|
|
521
|
-
}
|
|
522
|
-
if (shouldIndicateWasmVerification(confidence)) {
|
|
523
|
-
return "wasm-enhanced";
|
|
524
|
-
}
|
|
525
|
-
return "pattern";
|
|
526
|
-
}
|
|
527
|
-
var init_wasm_confidence = __esm({
|
|
528
|
-
"src/wasm-confidence.ts"() {
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
ed25519__namespace.etc.sha512Sync = (...m) => sha2_js.sha512(ed25519__namespace.etc.concatBytes(...m));
|
|
532
|
-
var KNOWN_KEYS = {
|
|
533
|
-
chatgpt: [
|
|
534
|
-
{
|
|
535
|
-
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
536
|
-
// ChatGPT's current Ed25519 public key (base64)
|
|
537
|
-
// Source: https://chatgpt.com/.well-known/http-message-signatures-directory
|
|
538
|
-
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
539
|
-
validFrom: 1735689600,
|
|
540
|
-
// Jan 1, 2025 (nbf from OpenAI)
|
|
541
|
-
validUntil: 1769029093
|
|
542
|
-
// Jan 21, 2026 (exp from OpenAI)
|
|
543
|
-
}
|
|
544
|
-
]
|
|
545
|
-
};
|
|
546
|
-
var keyCache = /* @__PURE__ */ new Map();
|
|
547
|
-
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
548
|
-
var CACHE_MAX_SIZE = 100;
|
|
549
|
-
function getApiBaseUrl() {
|
|
550
|
-
if (typeof window !== "undefined") {
|
|
551
|
-
return "/api/internal";
|
|
552
|
-
}
|
|
553
|
-
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);
|
|
554
|
-
if (baseUrl2) {
|
|
555
|
-
return baseUrl2.replace(/\/$/, "") + "/api/internal";
|
|
556
|
-
}
|
|
557
|
-
if (process.env.NODE_ENV !== "production") {
|
|
558
|
-
console.warn(
|
|
559
|
-
"[Signature] No base URL configured for server-side fetch. Using localhost fallback."
|
|
560
|
-
);
|
|
561
|
-
return "http://localhost:3000/api/internal";
|
|
562
|
-
}
|
|
563
|
-
console.error(
|
|
564
|
-
"[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
|
|
565
|
-
);
|
|
566
|
-
return "/api/internal";
|
|
567
|
-
}
|
|
568
|
-
function cleanupExpiredCache() {
|
|
569
|
-
const now = Date.now();
|
|
570
|
-
const entriesToDelete = [];
|
|
571
|
-
for (const [agent, cached] of keyCache.entries()) {
|
|
572
|
-
if (now - cached.cachedAt > CACHE_TTL_MS) {
|
|
573
|
-
entriesToDelete.push(agent);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
for (const agent of entriesToDelete) {
|
|
577
|
-
keyCache.delete(agent);
|
|
578
|
-
}
|
|
579
|
-
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
580
|
-
const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
|
|
581
|
-
agent,
|
|
582
|
-
cachedAt: cached.cachedAt
|
|
583
|
-
}));
|
|
584
|
-
entries.sort((a, b) => a.cachedAt - b.cachedAt);
|
|
585
|
-
const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
|
|
586
|
-
for (const entry of toRemove) {
|
|
587
|
-
keyCache.delete(entry.agent);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
async function fetchKeysFromApi(agent) {
|
|
592
|
-
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
593
|
-
cleanupExpiredCache();
|
|
594
|
-
}
|
|
595
|
-
const cached = keyCache.get(agent);
|
|
596
|
-
if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
|
|
597
|
-
return cached.keys;
|
|
598
|
-
}
|
|
599
|
-
if (typeof fetch === "undefined") {
|
|
600
|
-
console.warn("[Signature] fetch() not available in this environment");
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
try {
|
|
604
|
-
const apiBaseUrl = getApiBaseUrl();
|
|
605
|
-
const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
|
|
606
|
-
const response = await fetch(url, {
|
|
607
|
-
method: "GET",
|
|
608
|
-
headers: {
|
|
609
|
-
"Content-Type": "application/json"
|
|
610
|
-
},
|
|
611
|
-
// 5 second timeout
|
|
612
|
-
signal: AbortSignal.timeout(5e3)
|
|
613
|
-
});
|
|
614
|
-
if (!response.ok) {
|
|
615
|
-
console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
const data = await response.json();
|
|
619
|
-
if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
|
|
620
|
-
console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
keyCache.set(agent, {
|
|
624
|
-
keys: data.keys,
|
|
625
|
-
cachedAt: Date.now()
|
|
626
|
-
});
|
|
627
|
-
return data.keys;
|
|
628
|
-
} catch (error) {
|
|
629
|
-
console.warn("[Signature] Error fetching keys from API, using fallback", {
|
|
630
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
631
|
-
agent
|
|
632
|
-
});
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
function isValidAgent(agent) {
|
|
637
|
-
return agent in KNOWN_KEYS;
|
|
638
|
-
}
|
|
639
|
-
async function getKeysForAgent(agent) {
|
|
640
|
-
const apiKeys = await fetchKeysFromApi(agent);
|
|
641
|
-
if (apiKeys && apiKeys.length > 0) {
|
|
642
|
-
return apiKeys;
|
|
643
|
-
}
|
|
644
|
-
if (isValidAgent(agent)) {
|
|
645
|
-
return KNOWN_KEYS[agent];
|
|
646
|
-
}
|
|
647
|
-
return [];
|
|
648
|
-
}
|
|
649
|
-
function parseSignatureInput(signatureInput) {
|
|
650
|
-
try {
|
|
651
|
-
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
652
|
-
if (!match) return null;
|
|
653
|
-
const [, headersList, params] = match;
|
|
654
|
-
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
655
|
-
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
656
|
-
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
657
|
-
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
658
|
-
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
659
|
-
return {
|
|
660
|
-
keyid: keyidMatch[1],
|
|
661
|
-
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
662
|
-
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
663
|
-
signedHeaders
|
|
664
|
-
};
|
|
665
|
-
} catch (error) {
|
|
666
|
-
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
667
|
-
return null;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
671
|
-
const components = [];
|
|
672
|
-
for (const headerName of signedHeaders) {
|
|
673
|
-
let value;
|
|
674
|
-
switch (headerName) {
|
|
675
|
-
case "@method":
|
|
676
|
-
value = method.toUpperCase();
|
|
677
|
-
break;
|
|
678
|
-
case "@path":
|
|
679
|
-
value = path;
|
|
680
|
-
break;
|
|
681
|
-
case "@authority":
|
|
682
|
-
value = headers["host"] || headers["Host"] || "";
|
|
683
|
-
break;
|
|
684
|
-
default: {
|
|
685
|
-
const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
|
|
686
|
-
value = key ? headers[key] || "" : "";
|
|
687
|
-
break;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
components.push(`"${headerName}": ${value}`);
|
|
691
|
-
}
|
|
692
|
-
return components.join("\n");
|
|
693
|
-
}
|
|
694
|
-
function base64ToBytes(base64) {
|
|
695
|
-
let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
696
|
-
const padding = standardBase64.length % 4;
|
|
697
|
-
if (padding) {
|
|
698
|
-
standardBase64 += "=".repeat(4 - padding);
|
|
699
|
-
}
|
|
700
|
-
const binaryString = atob(standardBase64);
|
|
701
|
-
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
702
|
-
}
|
|
703
|
-
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
704
|
-
try {
|
|
705
|
-
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
706
|
-
const signatureBytes = base64ToBytes(signatureBase64);
|
|
707
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
708
|
-
if (publicKeyBytes.length !== 32) {
|
|
709
|
-
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
710
|
-
return false;
|
|
711
|
-
}
|
|
712
|
-
if (signatureBytes.length !== 64) {
|
|
713
|
-
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
return ed25519__namespace.verify(signatureBytes, messageBytes, publicKeyBytes);
|
|
717
|
-
} catch (nobleError) {
|
|
718
|
-
console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
|
|
719
|
-
try {
|
|
720
|
-
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
721
|
-
const signatureBytes = base64ToBytes(signatureBase64);
|
|
722
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
723
|
-
const publicKey = await crypto.subtle.importKey(
|
|
724
|
-
"raw",
|
|
725
|
-
publicKeyBytes.buffer,
|
|
726
|
-
{
|
|
727
|
-
name: "Ed25519",
|
|
728
|
-
namedCurve: "Ed25519"
|
|
729
|
-
},
|
|
730
|
-
false,
|
|
731
|
-
["verify"]
|
|
732
|
-
);
|
|
733
|
-
return await crypto.subtle.verify(
|
|
734
|
-
"Ed25519",
|
|
735
|
-
publicKey,
|
|
736
|
-
signatureBytes.buffer,
|
|
737
|
-
messageBytes
|
|
738
|
-
);
|
|
739
|
-
} catch (cryptoError) {
|
|
740
|
-
console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
|
|
741
|
-
nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
|
|
742
|
-
cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
|
|
743
|
-
});
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
async function verifyAgentSignature(method, path, headers) {
|
|
749
|
-
const signature = headers["signature"] || headers["Signature"];
|
|
750
|
-
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
751
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
752
|
-
if (!signature || !signatureInput) {
|
|
753
|
-
return {
|
|
754
|
-
isValid: false,
|
|
755
|
-
confidence: 0,
|
|
756
|
-
reason: "No signature headers present",
|
|
757
|
-
verificationMethod: "none"
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
const parsed = parseSignatureInput(signatureInput);
|
|
761
|
-
if (!parsed) {
|
|
762
|
-
return {
|
|
763
|
-
isValid: false,
|
|
764
|
-
confidence: 0,
|
|
765
|
-
reason: "Invalid Signature-Input header",
|
|
766
|
-
verificationMethod: "none"
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
if (parsed.created) {
|
|
770
|
-
const now2 = Math.floor(Date.now() / 1e3);
|
|
771
|
-
const age = now2 - parsed.created;
|
|
772
|
-
if (age > 300) {
|
|
773
|
-
return {
|
|
774
|
-
isValid: false,
|
|
775
|
-
confidence: 0,
|
|
776
|
-
reason: "Signature expired (older than 5 minutes)",
|
|
777
|
-
verificationMethod: "none"
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
if (age < -30) {
|
|
781
|
-
return {
|
|
782
|
-
isValid: false,
|
|
783
|
-
confidence: 0,
|
|
784
|
-
reason: "Signature timestamp is in the future",
|
|
785
|
-
verificationMethod: "none"
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
let agent;
|
|
790
|
-
let agentKey;
|
|
791
|
-
const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
|
|
792
|
-
try {
|
|
793
|
-
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
794
|
-
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
795
|
-
} catch {
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
})();
|
|
799
|
-
if (isChatGPT) {
|
|
800
|
-
agent = "ChatGPT";
|
|
801
|
-
agentKey = "chatgpt";
|
|
802
|
-
}
|
|
803
|
-
if (!agent || !agentKey) {
|
|
804
|
-
return {
|
|
805
|
-
isValid: false,
|
|
806
|
-
confidence: 0,
|
|
807
|
-
reason: "Unknown signature agent",
|
|
808
|
-
verificationMethod: "none"
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
const knownKeys = await getKeysForAgent(agentKey);
|
|
812
|
-
if (knownKeys.length === 0) {
|
|
813
|
-
return {
|
|
814
|
-
isValid: false,
|
|
815
|
-
confidence: 0,
|
|
816
|
-
reason: "No keys available for agent",
|
|
817
|
-
verificationMethod: "none"
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
821
|
-
if (!key) {
|
|
822
|
-
return {
|
|
823
|
-
isValid: false,
|
|
824
|
-
confidence: 0,
|
|
825
|
-
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
826
|
-
verificationMethod: "none"
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
830
|
-
if (now < key.validFrom || now > key.validUntil) {
|
|
831
|
-
return {
|
|
832
|
-
isValid: false,
|
|
833
|
-
confidence: 0,
|
|
834
|
-
reason: "Key is not valid at current time",
|
|
835
|
-
verificationMethod: "none"
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
839
|
-
let signatureValue = signature;
|
|
840
|
-
if (signatureValue.startsWith("sig1=:")) {
|
|
841
|
-
signatureValue = signatureValue.substring(6);
|
|
842
|
-
}
|
|
843
|
-
if (signatureValue.endsWith(":")) {
|
|
844
|
-
signatureValue = signatureValue.slice(0, -1);
|
|
845
|
-
}
|
|
846
|
-
const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
|
|
847
|
-
if (isValid) {
|
|
848
|
-
return {
|
|
849
|
-
isValid: true,
|
|
850
|
-
agent,
|
|
851
|
-
keyid: parsed.keyid,
|
|
852
|
-
confidence: 1,
|
|
853
|
-
// 100% confidence for valid signature
|
|
854
|
-
verificationMethod: "signature"
|
|
855
|
-
};
|
|
856
|
-
} else {
|
|
857
|
-
return {
|
|
858
|
-
isValid: false,
|
|
859
|
-
confidence: 0,
|
|
860
|
-
reason: "Signature verification failed",
|
|
861
|
-
verificationMethod: "none"
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
function hasSignatureHeaders(headers) {
|
|
866
|
-
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
867
|
-
}
|
|
868
|
-
function isChatGPTSignature(headers) {
|
|
869
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
870
|
-
if (!signatureAgent) {
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
873
|
-
const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
|
|
874
|
-
if (agentUrlStr === "https://chatgpt.com") {
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
const agentUrl = new URL(agentUrlStr);
|
|
879
|
-
const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
|
|
880
|
-
return allowedHosts.includes(agentUrl.host);
|
|
881
|
-
} catch {
|
|
882
|
-
return false;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
var rules = agentshieldShared.loadRulesSync();
|
|
886
|
-
var EdgeAgentDetector = class {
|
|
887
|
-
rules;
|
|
888
|
-
constructor() {
|
|
889
|
-
this.rules = rules;
|
|
890
|
-
}
|
|
891
|
-
async analyze(input) {
|
|
892
|
-
const reasons = [];
|
|
893
|
-
let detectedAgent;
|
|
894
|
-
let verificationMethod;
|
|
895
|
-
let confidence = 0;
|
|
896
|
-
const headers = input.headers || {};
|
|
897
|
-
const normalizedHeaders = {};
|
|
898
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
899
|
-
normalizedHeaders[key.toLowerCase()] = value;
|
|
900
|
-
}
|
|
901
|
-
if (hasSignatureHeaders(headers)) {
|
|
902
|
-
try {
|
|
903
|
-
const signatureResult = await verifyAgentSignature(
|
|
904
|
-
input.method || "GET",
|
|
905
|
-
input.url || "/",
|
|
906
|
-
headers
|
|
907
|
-
);
|
|
908
|
-
if (signatureResult.isValid) {
|
|
909
|
-
confidence = signatureResult.confidence * 100;
|
|
910
|
-
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
911
|
-
if (signatureResult.agent) {
|
|
912
|
-
detectedAgent = {
|
|
913
|
-
type: signatureResult.agent.toLowerCase(),
|
|
914
|
-
name: signatureResult.agent
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
verificationMethod = signatureResult.verificationMethod;
|
|
918
|
-
if (signatureResult.keyid) {
|
|
919
|
-
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
920
|
-
}
|
|
921
|
-
} else {
|
|
922
|
-
console.warn("[EdgeAgentDetector] Signature verification failed:", {
|
|
923
|
-
reason: signatureResult.reason,
|
|
924
|
-
agent: signatureResult.agent,
|
|
925
|
-
hasSignatureAgent: !!headers["signature-agent"] || !!headers["Signature-Agent"],
|
|
926
|
-
signatureAgentValue: headers["signature-agent"] || headers["Signature-Agent"]
|
|
927
|
-
});
|
|
928
|
-
confidence = Math.max(confidence, 30);
|
|
929
|
-
reasons.push("invalid_signature");
|
|
930
|
-
if (signatureResult.reason) {
|
|
931
|
-
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
932
|
-
}
|
|
933
|
-
if (isChatGPTSignature(headers)) {
|
|
934
|
-
reasons.push("claims_chatgpt");
|
|
935
|
-
detectedAgent = { type: "chatgpt", name: "ChatGPT (unverified)" };
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
} catch (error) {
|
|
939
|
-
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
940
|
-
confidence = Math.max(confidence, 20);
|
|
941
|
-
reasons.push("signature_verification_error");
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
945
|
-
if (userAgent) {
|
|
946
|
-
const userAgentEntries = Object.entries(this.rules.rules.userAgents);
|
|
947
|
-
const genericKeys = ["generic_bot", "dev_tools", "automation_tools"];
|
|
948
|
-
const sortedEntries = userAgentEntries.sort((a, b) => {
|
|
949
|
-
const aIsGeneric = genericKeys.includes(a[0]);
|
|
950
|
-
const bIsGeneric = genericKeys.includes(b[0]);
|
|
951
|
-
if (aIsGeneric && !bIsGeneric) return 1;
|
|
952
|
-
if (!aIsGeneric && bIsGeneric) return -1;
|
|
953
|
-
return 0;
|
|
954
|
-
});
|
|
955
|
-
for (const [agentKey, agentRule] of sortedEntries) {
|
|
956
|
-
const rule = agentRule;
|
|
957
|
-
const matched = rule.patterns.some((pattern) => {
|
|
958
|
-
const regex = new RegExp(pattern, "i");
|
|
959
|
-
return regex.test(userAgent);
|
|
960
|
-
});
|
|
961
|
-
if (matched) {
|
|
962
|
-
const agentType = this.getAgentType(agentKey);
|
|
963
|
-
const agentName = this.getAgentName(agentKey);
|
|
964
|
-
confidence = Math.max(confidence, rule.confidence * 100);
|
|
965
|
-
reasons.push(`known_pattern:${agentType}`);
|
|
966
|
-
if (!detectedAgent) {
|
|
967
|
-
detectedAgent = { type: agentType, name: agentName };
|
|
968
|
-
verificationMethod = "pattern";
|
|
969
|
-
}
|
|
970
|
-
break;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
const suspiciousHeaders = this.rules.rules.headers.suspicious;
|
|
975
|
-
const foundAiHeaders = suspiciousHeaders.filter(
|
|
976
|
-
(headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
|
|
977
|
-
);
|
|
978
|
-
if (foundAiHeaders.length > 0) {
|
|
979
|
-
const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence * 100));
|
|
980
|
-
confidence = Math.max(confidence, maxConfidence);
|
|
981
|
-
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
982
|
-
}
|
|
983
|
-
const ip = input.ip || input.ipAddress;
|
|
984
|
-
if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
|
|
985
|
-
const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
|
|
986
|
-
for (const [provider, ipRule] of Object.entries(ipRanges)) {
|
|
987
|
-
if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
|
|
988
|
-
continue;
|
|
989
|
-
const matched = ipRule.ranges.some((range) => {
|
|
990
|
-
const prefix = range.split("/")[0];
|
|
991
|
-
const prefixParts = prefix.split(".");
|
|
992
|
-
const ipParts = ip.split(".");
|
|
993
|
-
for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
|
|
994
|
-
if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
|
|
995
|
-
return false;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
return true;
|
|
999
|
-
});
|
|
1000
|
-
if (matched) {
|
|
1001
|
-
const rule = ipRule;
|
|
1002
|
-
confidence = Math.max(confidence, rule.confidence * 40);
|
|
1003
|
-
reasons.push(`cloud_provider:${provider}`);
|
|
1004
|
-
break;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (reasons.length > 2 && confidence < 100) {
|
|
1009
|
-
confidence = Math.min(confidence * 1.2, 95);
|
|
1010
|
-
}
|
|
1011
|
-
confidence = Math.min(Math.max(confidence, 0), 100);
|
|
1012
|
-
return {
|
|
1013
|
-
isAgent: confidence > 30,
|
|
1014
|
-
// Updated to 0-100 scale (was 0.3)
|
|
1015
|
-
confidence,
|
|
1016
|
-
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1017
|
-
signals: [],
|
|
1018
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
1019
|
-
...detectedAgent && { detectedAgent },
|
|
1020
|
-
reasons,
|
|
1021
|
-
...verificationMethod && {
|
|
1022
|
-
verificationMethod
|
|
1023
|
-
},
|
|
1024
|
-
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 80 ? "medium" : "high",
|
|
1025
|
-
// Updated to 0-100 scale
|
|
1026
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Get agent type from rule key
|
|
1031
|
-
*/
|
|
1032
|
-
getAgentType(agentKey) {
|
|
1033
|
-
const typeMap = {
|
|
1034
|
-
openai_gptbot: "openai",
|
|
1035
|
-
anthropic_claude: "anthropic",
|
|
1036
|
-
perplexity_bot: "perplexity",
|
|
1037
|
-
google_ai: "google",
|
|
1038
|
-
microsoft_ai: "microsoft",
|
|
1039
|
-
meta_ai: "meta",
|
|
1040
|
-
cohere_bot: "cohere",
|
|
1041
|
-
huggingface_bot: "huggingface",
|
|
1042
|
-
generic_bot: "generic",
|
|
1043
|
-
dev_tools: "dev",
|
|
1044
|
-
automation_tools: "automation"
|
|
1045
|
-
};
|
|
1046
|
-
return typeMap[agentKey] || agentKey;
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* Get agent name from rule key
|
|
1050
|
-
*/
|
|
1051
|
-
getAgentName(agentKey) {
|
|
1052
|
-
const nameMap = {
|
|
1053
|
-
openai_gptbot: "ChatGPT/GPTBot",
|
|
1054
|
-
anthropic_claude: "Claude",
|
|
1055
|
-
perplexity_bot: "Perplexity",
|
|
1056
|
-
google_ai: "Google AI",
|
|
1057
|
-
microsoft_ai: "Microsoft Copilot",
|
|
1058
|
-
meta_ai: "Meta AI",
|
|
1059
|
-
cohere_bot: "Cohere",
|
|
1060
|
-
huggingface_bot: "HuggingFace",
|
|
1061
|
-
generic_bot: "Generic Bot",
|
|
1062
|
-
dev_tools: "Development Tool",
|
|
1063
|
-
automation_tools: "Automation Tool"
|
|
1064
|
-
};
|
|
1065
|
-
return nameMap[agentKey] || agentKey;
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
var EdgeAgentDetectorWrapper = class {
|
|
1069
|
-
detector;
|
|
1070
|
-
events = /* @__PURE__ */ new Map();
|
|
1071
|
-
constructor(_config) {
|
|
1072
|
-
this.detector = new EdgeAgentDetector();
|
|
1073
|
-
}
|
|
1074
|
-
async analyze(input) {
|
|
1075
|
-
const result = await this.detector.analyze(input);
|
|
1076
|
-
if (result.isAgent && this.events.has("agent.detected")) {
|
|
1077
|
-
const handlers = this.events.get("agent.detected") || [];
|
|
1078
|
-
handlers.forEach((handler) => handler(result, input));
|
|
1079
|
-
}
|
|
1080
|
-
return result;
|
|
1081
|
-
}
|
|
1082
|
-
on(event, handler) {
|
|
1083
|
-
if (!this.events.has(event)) {
|
|
1084
|
-
this.events.set(event, []);
|
|
1085
|
-
}
|
|
1086
|
-
this.events.get(event).push(handler);
|
|
1087
|
-
}
|
|
1088
|
-
emit(event, ...args) {
|
|
1089
|
-
const handlers = this.events.get(event) || [];
|
|
1090
|
-
handlers.forEach((handler) => handler(...args));
|
|
1091
|
-
}
|
|
1092
|
-
async init() {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
var EdgeSessionTracker = class {
|
|
1097
|
-
config;
|
|
1098
|
-
constructor(config) {
|
|
1099
|
-
this.config = {
|
|
1100
|
-
enabled: config.enabled,
|
|
1101
|
-
cookieName: config.cookieName || "__agentshield_session",
|
|
1102
|
-
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
1103
|
-
// 1 hour default
|
|
1104
|
-
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Track a new AI agent session
|
|
1109
|
-
*/
|
|
1110
|
-
async track(_request, response, result) {
|
|
1111
|
-
try {
|
|
1112
|
-
if (!this.config.enabled || !agentshieldShared.shouldEnforce(result)) {
|
|
1113
|
-
return response;
|
|
1114
|
-
}
|
|
1115
|
-
const sessionData = {
|
|
1116
|
-
id: crypto.randomUUID(),
|
|
1117
|
-
agent: result.detectedAgent?.name || "unknown",
|
|
1118
|
-
confidence: result.confidence,
|
|
1119
|
-
detectedAt: Date.now(),
|
|
1120
|
-
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
1121
|
-
};
|
|
1122
|
-
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
1123
|
-
response.cookies.set(this.config.cookieName, encrypted, {
|
|
1124
|
-
httpOnly: true,
|
|
1125
|
-
secure: process.env.NODE_ENV === "production",
|
|
1126
|
-
sameSite: "lax",
|
|
1127
|
-
maxAge: this.config.cookieMaxAge,
|
|
1128
|
-
path: "/"
|
|
1129
|
-
});
|
|
1130
|
-
return response;
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
1133
|
-
console.warn("AgentShield: Failed to track session:", error);
|
|
1134
|
-
}
|
|
1135
|
-
return response;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Check for existing AI agent session
|
|
1140
|
-
*/
|
|
1141
|
-
async check(request) {
|
|
1142
|
-
try {
|
|
1143
|
-
if (!this.config.enabled) {
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
const cookie = request.cookies.get(this.config.cookieName);
|
|
1147
|
-
if (!cookie?.value) {
|
|
1148
|
-
return null;
|
|
1149
|
-
}
|
|
1150
|
-
const decrypted = await this.decrypt(cookie.value);
|
|
1151
|
-
const session = JSON.parse(decrypted);
|
|
1152
|
-
if (session.expires < Date.now()) {
|
|
1153
|
-
return null;
|
|
1154
|
-
}
|
|
1155
|
-
return session;
|
|
1156
|
-
} catch (error) {
|
|
1157
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
1158
|
-
console.warn("AgentShield: Failed to check session:", error);
|
|
1159
|
-
}
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Clear an existing session
|
|
1165
|
-
*/
|
|
1166
|
-
clear(response) {
|
|
1167
|
-
try {
|
|
1168
|
-
response.cookies.delete(this.config.cookieName);
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
1171
|
-
console.warn("AgentShield: Failed to clear session:", error);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
return response;
|
|
1175
|
-
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Simple encryption using Web Crypto API (Edge-compatible)
|
|
1178
|
-
*/
|
|
1179
|
-
async encrypt(data) {
|
|
1180
|
-
try {
|
|
1181
|
-
const key = this.config.encryptionKey;
|
|
1182
|
-
const encoded = new TextEncoder().encode(data);
|
|
1183
|
-
const obfuscated = new Uint8Array(encoded.length);
|
|
1184
|
-
for (let i = 0; i < encoded.length; i++) {
|
|
1185
|
-
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
1186
|
-
}
|
|
1187
|
-
return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
|
|
1188
|
-
} catch (error) {
|
|
1189
|
-
return btoa(data);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Simple decryption (Edge-compatible)
|
|
1194
|
-
*/
|
|
1195
|
-
async decrypt(data) {
|
|
1196
|
-
try {
|
|
1197
|
-
const key = this.config.encryptionKey;
|
|
1198
|
-
const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
1199
|
-
const deobfuscated = new Uint8Array(decoded.length);
|
|
1200
|
-
for (let i = 0; i < decoded.length; i++) {
|
|
1201
|
-
deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
1202
|
-
}
|
|
1203
|
-
return new TextDecoder().decode(deobfuscated);
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
return atob(data);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
var StatelessSessionChecker = class {
|
|
1210
|
-
static check(headers) {
|
|
1211
|
-
try {
|
|
1212
|
-
const agent = headers["kya-session-agent"];
|
|
1213
|
-
const confidence = headers["kya-session-confidence"];
|
|
1214
|
-
const sessionId = headers["kya-session-id"];
|
|
1215
|
-
if (agent && confidence && sessionId) {
|
|
1216
|
-
return {
|
|
1217
|
-
id: sessionId,
|
|
1218
|
-
agent,
|
|
1219
|
-
confidence: parseFloat(confidence),
|
|
1220
|
-
detectedAt: Date.now(),
|
|
1221
|
-
expires: Date.now() + 36e5
|
|
1222
|
-
// 1 hour
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
const cookieHeader = headers["cookie"];
|
|
1226
|
-
if (cookieHeader && cookieHeader.includes("__agentshield_session=")) {
|
|
1227
|
-
const match = cookieHeader.match(/__agentshield_session=([^;]+)/);
|
|
1228
|
-
if (match && match[1]) {
|
|
1229
|
-
try {
|
|
1230
|
-
const decoded = atob(match[1]);
|
|
1231
|
-
return JSON.parse(decoded);
|
|
1232
|
-
} catch {
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
return null;
|
|
1237
|
-
} catch {
|
|
1238
|
-
return null;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
static setHeaders(response, session) {
|
|
1242
|
-
try {
|
|
1243
|
-
if (response.setHeader) {
|
|
1244
|
-
response.setHeader("KYA-Session-Agent", session.agent);
|
|
1245
|
-
response.setHeader("KYA-Session-Confidence", session.confidence.toString());
|
|
1246
|
-
response.setHeader("KYA-Session-Id", session.id);
|
|
1247
|
-
} else if (response.headers && response.headers.set) {
|
|
1248
|
-
response.headers.set("kya-session-agent", session.agent);
|
|
1249
|
-
response.headers.set("kya-session-confidence", session.confidence.toString());
|
|
1250
|
-
response.headers.set("kya-session-id", session.id);
|
|
1251
|
-
}
|
|
1252
|
-
} catch {
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
};
|
|
1256
|
-
|
|
1257
|
-
// src/utils.ts
|
|
1258
|
-
function getClientIp(request) {
|
|
1259
|
-
const forwardedFor = request.headers.get("x-forwarded-for");
|
|
1260
|
-
if (forwardedFor) {
|
|
1261
|
-
const ip = forwardedFor.split(",")[0]?.trim();
|
|
1262
|
-
if (ip) return ip;
|
|
1263
|
-
}
|
|
1264
|
-
const realIp = request.headers.get("x-real-ip");
|
|
1265
|
-
if (realIp) return realIp;
|
|
1266
|
-
const cfIp = request.headers.get("cf-connecting-ip");
|
|
1267
|
-
if (cfIp) return cfIp;
|
|
1268
|
-
const clientIp = request.headers.get("x-client-ip");
|
|
1269
|
-
if (clientIp) return clientIp;
|
|
1270
|
-
return void 0;
|
|
1271
|
-
}
|
|
1272
|
-
function safeHostname(url) {
|
|
1273
|
-
try {
|
|
1274
|
-
return new URL(url).hostname;
|
|
1275
|
-
} catch {
|
|
1276
|
-
return "this site";
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
function createAgentShieldMiddleware(config = {}) {
|
|
1280
|
-
let detector = config.enableWasm ? null : new EdgeAgentDetectorWrapper(config);
|
|
1281
|
-
let detectorInitPromise = null;
|
|
1282
|
-
const sessionTracker = config.sessionTracking?.enabled || config.enableWasm ? new EdgeSessionTracker({
|
|
1283
|
-
enabled: true,
|
|
1284
|
-
...config.sessionTracking
|
|
1285
|
-
}) : null;
|
|
1286
|
-
if (detector && config.events) {
|
|
1287
|
-
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1288
|
-
if (handler) {
|
|
1289
|
-
detector.on(event, handler);
|
|
1290
|
-
}
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
const {
|
|
1294
|
-
onAgentDetected = "log",
|
|
1295
|
-
onDetection,
|
|
1296
|
-
skipPaths = [],
|
|
1297
|
-
blockedResponse = {
|
|
1298
|
-
status: 403,
|
|
1299
|
-
message: "Access denied: Automated agent detected",
|
|
1300
|
-
headers: { "Content-Type": "application/json" }
|
|
1301
|
-
},
|
|
1302
|
-
redirectUrl = "/blocked",
|
|
1303
|
-
rewriteUrl = "/blocked"
|
|
1304
|
-
} = config;
|
|
1305
|
-
return async (request) => {
|
|
1306
|
-
try {
|
|
1307
|
-
if (!detector) {
|
|
1308
|
-
if (!detectorInitPromise) {
|
|
1309
|
-
detectorInitPromise = (async () => {
|
|
1310
|
-
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1311
|
-
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1312
|
-
if (config.events) {
|
|
1313
|
-
Object.entries(config.events).forEach(([event, handler]) => {
|
|
1314
|
-
if (handler) {
|
|
1315
|
-
detector.on(event, handler);
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
})();
|
|
1320
|
-
}
|
|
1321
|
-
await detectorInitPromise;
|
|
1322
|
-
}
|
|
1323
|
-
const activeDetector = detector;
|
|
1324
|
-
const shouldSkip = skipPaths.some((pattern) => {
|
|
1325
|
-
if (typeof pattern === "string") {
|
|
1326
|
-
return request.nextUrl.pathname.startsWith(pattern);
|
|
1327
|
-
}
|
|
1328
|
-
return pattern.test(request.nextUrl.pathname);
|
|
1329
|
-
});
|
|
1330
|
-
if (shouldSkip) {
|
|
1331
|
-
request.agentShield = { skipped: true };
|
|
1332
|
-
return server.NextResponse.next();
|
|
1333
|
-
}
|
|
1334
|
-
const existingSession = sessionTracker ? await sessionTracker.check(request) : null;
|
|
1335
|
-
if (existingSession) {
|
|
1336
|
-
const response2 = server.NextResponse.next();
|
|
1337
|
-
response2.headers.set("kya-detected", "true");
|
|
1338
|
-
response2.headers.set("kya-agent", existingSession.agent);
|
|
1339
|
-
response2.headers.set("kya-confidence", existingSession.confidence.toString());
|
|
1340
|
-
response2.headers.set("kya-session", "continued");
|
|
1341
|
-
response2.headers.set("kya-session-id", existingSession.id);
|
|
1342
|
-
request.agentShield = {
|
|
1343
|
-
result: {
|
|
1344
|
-
isAgent: true,
|
|
1345
|
-
confidence: existingSession.confidence,
|
|
1346
|
-
detectionClass: { type: "AiAgent" },
|
|
1347
|
-
detectedAgent: {
|
|
1348
|
-
type: "ai_agent",
|
|
1349
|
-
name: existingSession.agent
|
|
1350
|
-
},
|
|
1351
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
1352
|
-
verificationMethod: "behavioral",
|
|
1353
|
-
reasons: ["Session continued"],
|
|
1354
|
-
signals: []
|
|
1355
|
-
},
|
|
1356
|
-
session: existingSession,
|
|
1357
|
-
skipped: false
|
|
1358
|
-
};
|
|
1359
|
-
const context2 = {
|
|
1360
|
-
userAgent: request.headers.get("user-agent") || "",
|
|
1361
|
-
ipAddress: getClientIp(request) || "",
|
|
1362
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
1363
|
-
url: request.url,
|
|
1364
|
-
method: request.method,
|
|
1365
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1366
|
-
};
|
|
1367
|
-
activeDetector.emit("agent.session.continued", existingSession, context2);
|
|
1368
|
-
return response2;
|
|
1369
|
-
}
|
|
1370
|
-
const userAgent = request.headers.get("user-agent");
|
|
1371
|
-
const ipAddress = getClientIp(request);
|
|
1372
|
-
const url = new URL(request.url);
|
|
1373
|
-
const pathWithQuery = url.pathname + url.search;
|
|
1374
|
-
const context = {
|
|
1375
|
-
...userAgent && { userAgent },
|
|
1376
|
-
...ipAddress && { ipAddress },
|
|
1377
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
1378
|
-
url: pathWithQuery,
|
|
1379
|
-
// Use path instead of full URL for signature verification
|
|
1380
|
-
method: request.method,
|
|
1381
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1382
|
-
};
|
|
1383
|
-
const result = await activeDetector.analyze(context);
|
|
1384
|
-
const decision = agentshieldShared.evaluateEnforcement(result, {
|
|
1385
|
-
confidenceThreshold: config.confidenceThreshold,
|
|
1386
|
-
defaultAction: onAgentDetected
|
|
1387
|
-
});
|
|
1388
|
-
if (decision.shouldNotify) {
|
|
1389
|
-
if (onDetection) {
|
|
1390
|
-
const customResponse = await onDetection(request, result);
|
|
1391
|
-
if (customResponse) {
|
|
1392
|
-
return customResponse;
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
switch (decision.action) {
|
|
1396
|
-
case "block": {
|
|
1397
|
-
const response2 = server.NextResponse.json(
|
|
1398
|
-
{
|
|
1399
|
-
error: blockedResponse.message,
|
|
1400
|
-
detected: true,
|
|
1401
|
-
confidence: result.confidence,
|
|
1402
|
-
timestamp: result.timestamp
|
|
1403
|
-
},
|
|
1404
|
-
{ status: blockedResponse.status }
|
|
1405
|
-
);
|
|
1406
|
-
if (blockedResponse.headers) {
|
|
1407
|
-
Object.entries(blockedResponse.headers).forEach(([key, value]) => {
|
|
1408
|
-
response2.headers.set(key, value);
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
activeDetector.emit("agent.blocked", result, context);
|
|
1412
|
-
return response2;
|
|
1413
|
-
}
|
|
1414
|
-
case "redirect": {
|
|
1415
|
-
const redirectTarget = new URL(redirectUrl, request.url);
|
|
1416
|
-
const agentName = result.detectedAgent?.name;
|
|
1417
|
-
if (agentName && !redirectTarget.searchParams.has("agent")) {
|
|
1418
|
-
redirectTarget.searchParams.set("agent", agentName.toLowerCase());
|
|
1419
|
-
}
|
|
1420
|
-
return server.NextResponse.redirect(redirectTarget);
|
|
1421
|
-
}
|
|
1422
|
-
case "rewrite":
|
|
1423
|
-
return server.NextResponse.rewrite(new URL(rewriteUrl, request.url));
|
|
1424
|
-
case "log":
|
|
1425
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1426
|
-
console.debug("AgentShield: Agent detected", {
|
|
1427
|
-
ipAddress: context.ipAddress,
|
|
1428
|
-
userAgent: context.userAgent,
|
|
1429
|
-
confidence: result.confidence,
|
|
1430
|
-
reasons: result.reasons,
|
|
1431
|
-
pathname: request.nextUrl.pathname
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
break;
|
|
1435
|
-
case "allow":
|
|
1436
|
-
default:
|
|
1437
|
-
activeDetector.emit("agent.allowed", result, context);
|
|
1438
|
-
break;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
request.agentShield = {
|
|
1442
|
-
result,
|
|
1443
|
-
skipped: false
|
|
1444
|
-
};
|
|
1445
|
-
let response = server.NextResponse.next();
|
|
1446
|
-
response.headers.set("kya-detected", result.isAgent.toString());
|
|
1447
|
-
response.headers.set("kya-confidence", result.confidence.toString());
|
|
1448
|
-
if (result.detectedAgent?.name) {
|
|
1449
|
-
response.headers.set("kya-agent", result.detectedAgent.name);
|
|
1450
|
-
}
|
|
1451
|
-
if (sessionTracker && decision.shouldNotify) {
|
|
1452
|
-
response = await sessionTracker.track(request, response, result);
|
|
1453
|
-
response.headers.set("kya-session", "new");
|
|
1454
|
-
activeDetector.emit("agent.session.started", result, context);
|
|
1455
|
-
}
|
|
1456
|
-
return response;
|
|
1457
|
-
} catch (error) {
|
|
1458
|
-
console.error("AgentShield middleware error:", error);
|
|
1459
|
-
return server.NextResponse.next();
|
|
1460
|
-
}
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// src/create-middleware.ts
|
|
1465
|
-
var middlewareInstance = null;
|
|
1466
|
-
var isInitializing = false;
|
|
1467
|
-
var initPromise2 = null;
|
|
1468
|
-
function createAgentShieldMiddleware2(config) {
|
|
1469
|
-
return async function agentShieldMiddleware2(request) {
|
|
1470
|
-
if (!middlewareInstance) {
|
|
1471
|
-
if (!isInitializing) {
|
|
1472
|
-
isInitializing = true;
|
|
1473
|
-
initPromise2 = (async () => {
|
|
1474
|
-
middlewareInstance = createAgentShieldMiddleware(config);
|
|
1475
|
-
return middlewareInstance;
|
|
1476
|
-
})();
|
|
1477
|
-
}
|
|
1478
|
-
if (initPromise2) {
|
|
1479
|
-
middlewareInstance = await initPromise2;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
return middlewareInstance ? middlewareInstance(request) : server.NextResponse.next();
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// src/edge-safe-detector.ts
|
|
1487
|
-
var AI_AGENT_PATTERNS = [
|
|
1488
|
-
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
1489
|
-
{ pattern: /claude-web/i, type: "claude", name: "Claude" },
|
|
1490
|
-
{ pattern: /claude-user/i, type: "claude", name: "Claude" },
|
|
1491
|
-
{ pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
|
|
1492
|
-
{ pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
|
|
1493
|
-
{ pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
|
|
1494
|
-
{ pattern: /bingbot/i, type: "bing", name: "Bing AI" },
|
|
1495
|
-
{ pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
|
|
1496
|
-
];
|
|
1497
|
-
var EdgeSafeDetector = class {
|
|
1498
|
-
async analyze(input) {
|
|
1499
|
-
const reasons = [];
|
|
1500
|
-
let detectedAgent;
|
|
1501
|
-
let confidence = 0;
|
|
1502
|
-
const headers = input.headers || {};
|
|
1503
|
-
const normalizedHeaders = {};
|
|
1504
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1505
|
-
normalizedHeaders[key.toLowerCase()] = value;
|
|
1506
|
-
}
|
|
1507
|
-
const userAgent = input.userAgent || normalizedHeaders["user-agent"] || "";
|
|
1508
|
-
if (userAgent) {
|
|
1509
|
-
for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
|
|
1510
|
-
if (pattern.test(userAgent)) {
|
|
1511
|
-
confidence = 85;
|
|
1512
|
-
reasons.push(`known_pattern:${type}`);
|
|
1513
|
-
detectedAgent = { type, name };
|
|
1514
|
-
break;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
const hasChrome = userAgent.toLowerCase().includes("chrome");
|
|
1519
|
-
const hasFirefox = userAgent.toLowerCase().includes("firefox");
|
|
1520
|
-
const hasSafari = userAgent.toLowerCase().includes("safari");
|
|
1521
|
-
const hasBrowserUA = hasChrome || hasFirefox || hasSafari;
|
|
1522
|
-
if (hasBrowserUA) {
|
|
1523
|
-
const hasSecChUa = !!normalizedHeaders["sec-ch-ua"];
|
|
1524
|
-
const hasSecFetch = !!normalizedHeaders["sec-fetch-site"];
|
|
1525
|
-
const hasAcceptLanguage = !!normalizedHeaders["accept-language"];
|
|
1526
|
-
const hasCookies = !!normalizedHeaders["cookie"];
|
|
1527
|
-
const missingHeaders = [];
|
|
1528
|
-
if (!hasSecChUa && hasChrome) missingHeaders.push("sec-ch-ua");
|
|
1529
|
-
if (!hasSecFetch) missingHeaders.push("sec-fetch");
|
|
1530
|
-
if (!hasAcceptLanguage) missingHeaders.push("accept-language");
|
|
1531
|
-
if (!hasCookies) missingHeaders.push("cookies");
|
|
1532
|
-
if (missingHeaders.length >= 2) {
|
|
1533
|
-
confidence = Math.max(confidence, 85);
|
|
1534
|
-
reasons.push("browser_ua_missing_headers");
|
|
1535
|
-
if (!detectedAgent && hasChrome && !hasSecChUa) {
|
|
1536
|
-
detectedAgent = { type: "perplexity", name: "Perplexity" };
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
const aiHeaders = ["openai-conversation-id", "anthropic-client-id", "x-goog-api-client"];
|
|
1541
|
-
const foundAiHeaders = aiHeaders.filter((h) => normalizedHeaders[h]);
|
|
1542
|
-
if (foundAiHeaders.length > 0) {
|
|
1543
|
-
confidence = Math.max(confidence, 60);
|
|
1544
|
-
reasons.push(`ai_headers:${foundAiHeaders.length}`);
|
|
1545
|
-
}
|
|
1546
|
-
return {
|
|
1547
|
-
isAgent: confidence > 30,
|
|
1548
|
-
// Updated to 0-100 scale (was 0.3)
|
|
1549
|
-
confidence,
|
|
1550
|
-
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
|
|
1551
|
-
signals: [],
|
|
1552
|
-
// Will be populated by enhanced detection engine in future tasks
|
|
1553
|
-
detectedAgent,
|
|
1554
|
-
reasons,
|
|
1555
|
-
verificationMethod: "pattern",
|
|
1556
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
1557
|
-
confidenceLevel: confidence >= 80 ? "high" : confidence >= 50 ? "medium" : "low"
|
|
1558
|
-
// Updated to 0-100 scale
|
|
1559
|
-
};
|
|
1560
|
-
}
|
|
1561
|
-
};
|
|
1562
|
-
|
|
1563
|
-
// src/storage/memory-adapter.ts
|
|
1564
|
-
var MemoryStorageAdapter = class {
|
|
1565
|
-
events = /* @__PURE__ */ new Map();
|
|
1566
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1567
|
-
eventTimeline = [];
|
|
1568
|
-
maxEvents = 1e3;
|
|
1569
|
-
maxSessions = 100;
|
|
1570
|
-
async storeEvent(event) {
|
|
1571
|
-
const eventKey = `${event.timestamp}:${event.eventId}`;
|
|
1572
|
-
this.events.set(eventKey, event);
|
|
1573
|
-
this.eventTimeline.push({
|
|
1574
|
-
timestamp: Date.parse(event.timestamp),
|
|
1575
|
-
eventId: eventKey
|
|
1576
|
-
});
|
|
1577
|
-
this.eventTimeline.sort((a, b) => b.timestamp - a.timestamp);
|
|
1578
|
-
if (this.eventTimeline.length > this.maxEvents) {
|
|
1579
|
-
const removed = this.eventTimeline.splice(this.maxEvents);
|
|
1580
|
-
removed.forEach((item) => this.events.delete(item.eventId));
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
async getRecentEvents(limit = 100) {
|
|
1584
|
-
const recent = this.eventTimeline.slice(0, limit);
|
|
1585
|
-
return recent.map((item) => this.events.get(item.eventId)).filter((event) => event !== void 0);
|
|
1586
|
-
}
|
|
1587
|
-
async getSessionEvents(sessionId) {
|
|
1588
|
-
const events = [];
|
|
1589
|
-
for (const event of this.events.values()) {
|
|
1590
|
-
if (event.sessionId === sessionId) {
|
|
1591
|
-
events.push(event);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
return events.sort(
|
|
1595
|
-
(a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
async storeSession(session) {
|
|
1599
|
-
this.sessions.set(session.sessionId, session);
|
|
1600
|
-
if (this.sessions.size > this.maxSessions) {
|
|
1601
|
-
const sortedSessions = Array.from(this.sessions.entries()).sort((a, b) => Date.parse(b[1].lastSeen) - Date.parse(a[1].lastSeen));
|
|
1602
|
-
const toRemove = sortedSessions.slice(this.maxSessions);
|
|
1603
|
-
toRemove.forEach(([id]) => this.sessions.delete(id));
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
async getSession(sessionId) {
|
|
1607
|
-
return this.sessions.get(sessionId) || null;
|
|
1608
|
-
}
|
|
1609
|
-
async getRecentSessions(limit = 10) {
|
|
1610
|
-
const sorted = Array.from(this.sessions.values()).sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen));
|
|
1611
|
-
return sorted.slice(0, limit);
|
|
1612
|
-
}
|
|
1613
|
-
async cleanup(olderThan) {
|
|
1614
|
-
const cutoff = olderThan.getTime();
|
|
1615
|
-
this.eventTimeline = this.eventTimeline.filter((item) => {
|
|
1616
|
-
if (item.timestamp < cutoff) {
|
|
1617
|
-
this.events.delete(item.eventId);
|
|
1618
|
-
return false;
|
|
1619
|
-
}
|
|
1620
|
-
return true;
|
|
1621
|
-
});
|
|
1622
|
-
for (const [id, session] of this.sessions.entries()) {
|
|
1623
|
-
if (Date.parse(session.lastSeen) < cutoff) {
|
|
1624
|
-
this.sessions.delete(id);
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
};
|
|
1629
|
-
|
|
1630
|
-
// src/storage/redis-adapter.ts
|
|
1631
|
-
var RedisStorageAdapter = class {
|
|
1632
|
-
redis;
|
|
1633
|
-
ttl;
|
|
1634
|
-
keyPrefix = "agent-shield";
|
|
1635
|
-
constructor(redis, ttl = 86400) {
|
|
1636
|
-
this.redis = redis;
|
|
1637
|
-
this.ttl = ttl;
|
|
1638
|
-
}
|
|
1639
|
-
eventKey(timestamp, eventId) {
|
|
1640
|
-
return `${this.keyPrefix}:events:${timestamp}:${eventId}`;
|
|
1641
|
-
}
|
|
1642
|
-
sessionKey(sessionId) {
|
|
1643
|
-
return `${this.keyPrefix}:sessions:${sessionId}`;
|
|
1644
|
-
}
|
|
1645
|
-
timelineKey() {
|
|
1646
|
-
return `${this.keyPrefix}:events:timeline`;
|
|
1647
|
-
}
|
|
1648
|
-
async storeEvent(event) {
|
|
1649
|
-
const key = this.eventKey(event.timestamp, event.eventId);
|
|
1650
|
-
await this.redis.setex(key, this.ttl, JSON.stringify(event));
|
|
1651
|
-
await this.redis.zadd(this.timelineKey(), {
|
|
1652
|
-
score: Date.parse(event.timestamp),
|
|
1653
|
-
member: key
|
|
1654
|
-
});
|
|
1655
|
-
const count = await this.redis.zcard(this.timelineKey());
|
|
1656
|
-
if (count && count > 1e3) {
|
|
1657
|
-
await this.redis.zremrangebyrank(this.timelineKey(), 0, -1001);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
async getRecentEvents(limit = 100) {
|
|
1661
|
-
const keys = await this.redis.zrevrange(this.timelineKey(), 0, limit - 1);
|
|
1662
|
-
if (!keys || keys.length === 0) {
|
|
1663
|
-
return [];
|
|
1664
|
-
}
|
|
1665
|
-
const events = [];
|
|
1666
|
-
for (const key of keys) {
|
|
1667
|
-
const data = await this.redis.get(key);
|
|
1668
|
-
if (data) {
|
|
1669
|
-
try {
|
|
1670
|
-
const event = typeof data === "string" ? JSON.parse(data) : data;
|
|
1671
|
-
events.push(event);
|
|
1672
|
-
} catch (e) {
|
|
1673
|
-
console.error(`Failed to parse event ${key}:`, e);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
return events;
|
|
1678
|
-
}
|
|
1679
|
-
async getSessionEvents(sessionId) {
|
|
1680
|
-
const keys = await this.redis.zrevrange(this.timelineKey(), 0, -1);
|
|
1681
|
-
if (!keys || keys.length === 0) {
|
|
1682
|
-
return [];
|
|
1683
|
-
}
|
|
1684
|
-
const events = [];
|
|
1685
|
-
for (const key of keys) {
|
|
1686
|
-
const data = await this.redis.get(key);
|
|
1687
|
-
if (data) {
|
|
1688
|
-
try {
|
|
1689
|
-
const event = typeof data === "string" ? JSON.parse(data) : data;
|
|
1690
|
-
if (event.sessionId === sessionId) {
|
|
1691
|
-
events.push(event);
|
|
1692
|
-
}
|
|
1693
|
-
} catch (e) {
|
|
1694
|
-
console.error(`Failed to parse event ${key}:`, e);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
return events;
|
|
1699
|
-
}
|
|
1700
|
-
async storeSession(session) {
|
|
1701
|
-
const key = this.sessionKey(session.sessionId);
|
|
1702
|
-
const existing = await this.redis.get(key);
|
|
1703
|
-
if (existing) {
|
|
1704
|
-
const existingSession = typeof existing === "string" ? JSON.parse(existing) : existing;
|
|
1705
|
-
const methods = /* @__PURE__ */ new Set([
|
|
1706
|
-
...existingSession.verificationMethods || [],
|
|
1707
|
-
...session.verificationMethods
|
|
1708
|
-
]);
|
|
1709
|
-
const updatedSession = {
|
|
1710
|
-
...existingSession,
|
|
1711
|
-
lastSeen: session.lastSeen,
|
|
1712
|
-
eventCount: session.eventCount,
|
|
1713
|
-
paths: Array.from(/* @__PURE__ */ new Set([...existingSession.paths, ...session.paths])),
|
|
1714
|
-
averageConfidence: session.averageConfidence,
|
|
1715
|
-
verificationMethods: Array.from(methods)
|
|
1716
|
-
};
|
|
1717
|
-
await this.redis.setex(key, this.ttl, JSON.stringify(updatedSession));
|
|
1718
|
-
} else {
|
|
1719
|
-
await this.redis.setex(key, this.ttl, JSON.stringify(session));
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
async getSession(sessionId) {
|
|
1723
|
-
const key = this.sessionKey(sessionId);
|
|
1724
|
-
const data = await this.redis.get(key);
|
|
1725
|
-
if (!data) {
|
|
1726
|
-
return null;
|
|
1727
|
-
}
|
|
1728
|
-
try {
|
|
1729
|
-
return typeof data === "string" ? JSON.parse(data) : data;
|
|
1730
|
-
} catch (e) {
|
|
1731
|
-
console.error(`Failed to parse session ${sessionId}:`, e);
|
|
1732
|
-
return null;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
async getRecentSessions(limit = 10) {
|
|
1736
|
-
const pattern = `${this.keyPrefix}:sessions:*`;
|
|
1737
|
-
const sessions = [];
|
|
1738
|
-
let cursor = 0;
|
|
1739
|
-
do {
|
|
1740
|
-
const [nextCursor, keys] = await this.redis.scan(cursor, {
|
|
1741
|
-
match: pattern,
|
|
1742
|
-
count: 100
|
|
1743
|
-
});
|
|
1744
|
-
cursor = parseInt(nextCursor);
|
|
1745
|
-
for (const key of keys) {
|
|
1746
|
-
const data = await this.redis.get(key);
|
|
1747
|
-
if (data) {
|
|
1748
|
-
try {
|
|
1749
|
-
const session = typeof data === "string" ? JSON.parse(data) : data;
|
|
1750
|
-
sessions.push(session);
|
|
1751
|
-
} catch (e) {
|
|
1752
|
-
console.error(`Failed to parse session from ${key}:`, e);
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
} while (cursor !== 0 && sessions.length < limit * 2);
|
|
1757
|
-
return sessions.sort((a, b) => Date.parse(b.lastSeen) - Date.parse(a.lastSeen)).slice(0, limit);
|
|
1758
|
-
}
|
|
1759
|
-
async cleanup(olderThan) {
|
|
1760
|
-
const cutoff = olderThan.getTime();
|
|
1761
|
-
await this.redis.zremrangebyrank(this.timelineKey(), 0, Math.floor(cutoff / 1e3));
|
|
1762
|
-
}
|
|
1763
|
-
};
|
|
1764
|
-
|
|
1765
|
-
// src/storage/index.ts
|
|
1766
|
-
async function createStorageAdapter(config) {
|
|
1767
|
-
if (!config || config.type === "memory") {
|
|
1768
|
-
return new MemoryStorageAdapter();
|
|
1769
|
-
}
|
|
1770
|
-
if (config.type === "custom" && config.custom) {
|
|
1771
|
-
return config.custom;
|
|
1772
|
-
}
|
|
1773
|
-
if (config.type === "redis" && config.redis) {
|
|
1774
|
-
try {
|
|
1775
|
-
const redisModuleName = "@upstash/redis";
|
|
1776
|
-
const redisModule = await import(
|
|
1777
|
-
/* webpackIgnore: true */
|
|
1778
|
-
redisModuleName
|
|
1779
|
-
);
|
|
1780
|
-
const Redis = redisModule.Redis;
|
|
1781
|
-
const redis = new Redis({
|
|
1782
|
-
url: config.redis.url,
|
|
1783
|
-
token: config.redis.token
|
|
1784
|
-
});
|
|
1785
|
-
return new RedisStorageAdapter(redis, config.ttl);
|
|
1786
|
-
} catch (error) {
|
|
1787
|
-
console.warn(
|
|
1788
|
-
"[AgentShield] Redis storage requires @upstash/redis package. Install with: npm install @upstash/redis",
|
|
1789
|
-
"\nFalling back to memory storage."
|
|
1790
|
-
);
|
|
1791
|
-
return new MemoryStorageAdapter();
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
return new MemoryStorageAdapter();
|
|
1795
|
-
}
|
|
1796
|
-
var SessionManager = class {
|
|
1797
|
-
sessionLastActivity = /* @__PURE__ */ new Map();
|
|
1798
|
-
generateSessionId(ipAddress, userAgent) {
|
|
1799
|
-
const now = Date.now();
|
|
1800
|
-
const timeWindow = Math.floor(now / (5 * 60 * 1e3));
|
|
1801
|
-
const baseKey = `${ipAddress || "unknown"}:${userAgent || "unknown"}`;
|
|
1802
|
-
const windowKey = `${baseKey}:${timeWindow}`;
|
|
1803
|
-
const lastActivity = this.sessionLastActivity.get(windowKey);
|
|
1804
|
-
const shouldCreateNewSession = !lastActivity || now - lastActivity > 3e4;
|
|
1805
|
-
this.sessionLastActivity.set(windowKey, now);
|
|
1806
|
-
if (this.sessionLastActivity.size > 100) {
|
|
1807
|
-
const cutoff = now - 6e5;
|
|
1808
|
-
for (const [key, time] of this.sessionLastActivity.entries()) {
|
|
1809
|
-
if (time < cutoff) {
|
|
1810
|
-
this.sessionLastActivity.delete(key);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
const data = shouldCreateNewSession ? `${windowKey}:${now}` : `${windowKey}:${lastActivity}`;
|
|
1815
|
-
let hash = 0;
|
|
1816
|
-
for (let i = 0; i < data.length; i++) {
|
|
1817
|
-
const char = data.charCodeAt(i);
|
|
1818
|
-
hash = (hash << 5) - hash + char;
|
|
1819
|
-
hash = hash & hash;
|
|
1820
|
-
}
|
|
1821
|
-
return Math.abs(hash).toString(16).padStart(12, "0").substring(0, 12);
|
|
1822
|
-
}
|
|
1823
|
-
};
|
|
1824
|
-
function createEnhancedAgentShieldMiddleware(config = {}) {
|
|
1825
|
-
let storageAdapter = null;
|
|
1826
|
-
let storageInitPromise = null;
|
|
1827
|
-
const getStorage = async () => {
|
|
1828
|
-
if (storageAdapter) return storageAdapter;
|
|
1829
|
-
if (storageInitPromise) return storageInitPromise;
|
|
1830
|
-
storageInitPromise = createStorageAdapter(config.storage).then((adapter) => {
|
|
1831
|
-
storageAdapter = adapter;
|
|
1832
|
-
return adapter;
|
|
1833
|
-
});
|
|
1834
|
-
return storageInitPromise;
|
|
1835
|
-
};
|
|
1836
|
-
let detector = null;
|
|
1837
|
-
let detectorInitPromise = null;
|
|
1838
|
-
let wasmConfidenceUtils = null;
|
|
1839
|
-
const getDetector = async (requestUrl) => {
|
|
1840
|
-
if (detector) {
|
|
1841
|
-
return detector;
|
|
1842
|
-
}
|
|
1843
|
-
if (detectorInitPromise) {
|
|
1844
|
-
await detectorInitPromise;
|
|
1845
|
-
return detector;
|
|
1846
|
-
}
|
|
1847
|
-
detectorInitPromise = (async () => {
|
|
1848
|
-
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== "undefined" || process.env.NEXT_RUNTIME === "edge";
|
|
1849
|
-
if (isEdgeRuntime) {
|
|
1850
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1851
|
-
console.debug("[AgentShield] Edge Runtime detected - using pattern detection");
|
|
1852
|
-
}
|
|
1853
|
-
detector = new EdgeSafeDetector();
|
|
1854
|
-
} else {
|
|
1855
|
-
try {
|
|
1856
|
-
try {
|
|
1857
|
-
const wasmUtils = await Promise.resolve().then(() => (init_wasm_confidence(), wasm_confidence_exports));
|
|
1858
|
-
wasmConfidenceUtils = wasmUtils;
|
|
1859
|
-
const wasmAvailable = await wasmUtils.checkWasmAvailability();
|
|
1860
|
-
if (wasmAvailable) {
|
|
1861
|
-
const { EdgeAgentDetectorWrapperWithWasm: EdgeAgentDetectorWrapperWithWasm2 } = await Promise.resolve().then(() => (init_edge_detector_with_wasm(), edge_detector_with_wasm_exports));
|
|
1862
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1863
|
-
console.debug(
|
|
1864
|
-
"[AgentShield] \u2705 WASM support detected - enhanced detection enabled"
|
|
1865
|
-
);
|
|
1866
|
-
}
|
|
1867
|
-
detector = new EdgeAgentDetectorWrapperWithWasm2({ enableWasm: true });
|
|
1868
|
-
if (requestUrl && "setBaseUrl" in detector) {
|
|
1869
|
-
detector.setBaseUrl(requestUrl);
|
|
1870
|
-
}
|
|
1871
|
-
} else {
|
|
1872
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1873
|
-
console.debug(
|
|
1874
|
-
"[AgentShield] \u2139\uFE0F Using pattern-based detection (WASM not available)"
|
|
1875
|
-
);
|
|
1876
|
-
}
|
|
1877
|
-
detector = new EdgeSafeDetector();
|
|
1878
|
-
}
|
|
1879
|
-
} catch (wasmError) {
|
|
1880
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1881
|
-
console.debug("[AgentShield] WASM utilities not available, using pattern detection");
|
|
1882
|
-
}
|
|
1883
|
-
detector = new EdgeSafeDetector();
|
|
1884
|
-
}
|
|
1885
|
-
} catch (error) {
|
|
1886
|
-
console.warn("[AgentShield] Failed to initialize enhanced detector, using fallback");
|
|
1887
|
-
detector = new EdgeSafeDetector();
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
})();
|
|
1891
|
-
await detectorInitPromise;
|
|
1892
|
-
return detector;
|
|
1893
|
-
};
|
|
1894
|
-
const sessionManager = new SessionManager();
|
|
1895
|
-
const sessionTrackingEnabled = config.sessionTracking?.enabled !== false;
|
|
1896
|
-
return async (request) => {
|
|
1897
|
-
const { pathname } = request.nextUrl;
|
|
1898
|
-
if (config.skipPaths?.some((path) => pathname.startsWith(path))) {
|
|
1899
|
-
return server.NextResponse.next();
|
|
1900
|
-
}
|
|
1901
|
-
const userAgent = request.headers.get("user-agent");
|
|
1902
|
-
const ipAddress = getClientIp(request);
|
|
1903
|
-
const url = new URL(request.url);
|
|
1904
|
-
const pathWithQuery = url.pathname + url.search;
|
|
1905
|
-
const context = {
|
|
1906
|
-
userAgent: userAgent || "",
|
|
1907
|
-
ipAddress: ipAddress || "",
|
|
1908
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
1909
|
-
url: pathWithQuery,
|
|
1910
|
-
method: request.method,
|
|
1911
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1912
|
-
};
|
|
1913
|
-
const activeDetector = await getDetector(request.url);
|
|
1914
|
-
const result = await activeDetector.analyze(context);
|
|
1915
|
-
let finalConfidence = result.confidence;
|
|
1916
|
-
let verificationMethod = result.verificationMethod || "pattern";
|
|
1917
|
-
if (agentshieldShared.shouldEnforce(result) && wasmConfidenceUtils) {
|
|
1918
|
-
const reasons = result.reasons || [];
|
|
1919
|
-
if (wasmConfidenceUtils.shouldIndicateWasmVerification(result.confidence)) {
|
|
1920
|
-
finalConfidence = wasmConfidenceUtils.getWasmConfidenceBoost(result.confidence, reasons);
|
|
1921
|
-
verificationMethod = wasmConfidenceUtils.getVerificationMethod(finalConfidence, reasons);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
const decision = agentshieldShared.evaluateEnforcement(
|
|
1925
|
-
{ ...result, confidence: finalConfidence },
|
|
1926
|
-
{
|
|
1927
|
-
confidenceThreshold: config.confidenceThreshold,
|
|
1928
|
-
defaultAction: config.onAgentDetected
|
|
1929
|
-
}
|
|
1930
|
-
);
|
|
1931
|
-
if (decision.shouldNotify) {
|
|
1932
|
-
if (sessionTrackingEnabled) {
|
|
1933
|
-
const storage = await getStorage();
|
|
1934
|
-
const sessionId = sessionManager.generateSessionId(
|
|
1935
|
-
ipAddress || void 0,
|
|
1936
|
-
userAgent || void 0
|
|
1937
|
-
);
|
|
1938
|
-
const event = {
|
|
1939
|
-
eventId: `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
1940
|
-
sessionId,
|
|
1941
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1942
|
-
agentType: result.detectedAgent?.type || "unknown",
|
|
1943
|
-
agentName: result.detectedAgent?.name || "Unknown",
|
|
1944
|
-
confidence: finalConfidence,
|
|
1945
|
-
path: pathWithQuery,
|
|
1946
|
-
...userAgent && { userAgent },
|
|
1947
|
-
...ipAddress && { ipAddress },
|
|
1948
|
-
method: request.method,
|
|
1949
|
-
detectionReasons: result.reasons || [],
|
|
1950
|
-
verificationMethod,
|
|
1951
|
-
detectionDetails: {
|
|
1952
|
-
patterns: result.detectedAgent?.patterns,
|
|
1953
|
-
behaviors: result.detectedAgent?.behaviors,
|
|
1954
|
-
fingerprintMatches: result.detectedAgent?.fingerprints
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
1957
|
-
try {
|
|
1958
|
-
await storage.storeEvent(event);
|
|
1959
|
-
let session = await storage.getSession(sessionId);
|
|
1960
|
-
if (session) {
|
|
1961
|
-
session.lastSeen = event.timestamp;
|
|
1962
|
-
session.eventCount++;
|
|
1963
|
-
if (!session.paths.includes(pathWithQuery)) {
|
|
1964
|
-
session.paths.push(pathWithQuery);
|
|
1965
|
-
}
|
|
1966
|
-
session.averageConfidence = (session.averageConfidence * (session.eventCount - 1) + finalConfidence) / session.eventCount;
|
|
1967
|
-
if (!session.verificationMethods.includes(verificationMethod)) {
|
|
1968
|
-
session.verificationMethods.push(verificationMethod);
|
|
1969
|
-
}
|
|
1970
|
-
} else {
|
|
1971
|
-
session = {
|
|
1972
|
-
sessionId,
|
|
1973
|
-
...ipAddress && { ipAddress },
|
|
1974
|
-
...userAgent && { userAgent },
|
|
1975
|
-
agentType: result.detectedAgent?.type || "unknown",
|
|
1976
|
-
agentName: result.detectedAgent?.name || "Unknown",
|
|
1977
|
-
firstSeen: event.timestamp,
|
|
1978
|
-
lastSeen: event.timestamp,
|
|
1979
|
-
eventCount: 1,
|
|
1980
|
-
paths: [pathWithQuery],
|
|
1981
|
-
averageConfidence: finalConfidence,
|
|
1982
|
-
verificationMethods: [verificationMethod]
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
if (session) {
|
|
1986
|
-
await storage.storeSession(session);
|
|
1987
|
-
}
|
|
1988
|
-
} catch (error) {
|
|
1989
|
-
console.error("[AgentShield] Failed to store event:", error);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
if (config.onDetection) {
|
|
1993
|
-
await config.onDetection(result, context);
|
|
1994
|
-
}
|
|
1995
|
-
switch (decision.action) {
|
|
1996
|
-
case "block": {
|
|
1997
|
-
const { status = 403, message = "Access denied: AI agent detected" } = config.blockedResponse || {};
|
|
1998
|
-
const response2 = server.NextResponse.json(
|
|
1999
|
-
{ error: message, detected: true, confidence: finalConfidence },
|
|
2000
|
-
{ status }
|
|
2001
|
-
);
|
|
2002
|
-
response2.headers.set("kya-detected", "true");
|
|
2003
|
-
response2.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
|
|
2004
|
-
response2.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
|
|
2005
|
-
response2.headers.set("kya-verification", verificationMethod);
|
|
2006
|
-
return response2;
|
|
2007
|
-
}
|
|
2008
|
-
case "log": {
|
|
2009
|
-
const isInteresting = finalConfidence >= 0.9 || result.detectedAgent?.name?.toLowerCase().includes("chatgpt") || result.detectedAgent?.name?.toLowerCase().includes("perplexity") || verificationMethod === "signature";
|
|
2010
|
-
if (isInteresting && process.env.NODE_ENV !== "production") {
|
|
2011
|
-
console.debug(`[AgentShield] AI Agent detected (${verificationMethod}):`, {
|
|
2012
|
-
agent: result.detectedAgent?.name,
|
|
2013
|
-
confidence: `${(finalConfidence * 100).toFixed(0)}%`,
|
|
2014
|
-
path: pathWithQuery,
|
|
2015
|
-
verification: verificationMethod
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
break;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
const response = server.NextResponse.next();
|
|
2023
|
-
if (result.isAgent) {
|
|
2024
|
-
response.headers.set("kya-detected", "true");
|
|
2025
|
-
response.headers.set("kya-confidence", String(Math.round(finalConfidence * 100)));
|
|
2026
|
-
response.headers.set("kya-agent", result.detectedAgent?.name || "Unknown");
|
|
2027
|
-
response.headers.set("kya-verification", verificationMethod);
|
|
2028
|
-
if (finalConfidence > 0.9) {
|
|
2029
|
-
response.headers.set("kya-ai-visitor", "true");
|
|
2030
|
-
response.headers.set("kya-ai-confidence", finalConfidence.toString());
|
|
2031
|
-
response.headers.set("kya-ai-verification", verificationMethod);
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
return response;
|
|
2035
|
-
};
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
// src/api-client.ts
|
|
2039
|
-
var DEFAULT_BASE_URL = "https://kya.vouched.id";
|
|
2040
|
-
var EDGE_DETECT_URL = "https://detect.checkpoint-gateway.ai";
|
|
2041
|
-
var DEFAULT_TIMEOUT = 5e3;
|
|
2042
|
-
var AgentShieldClient = class {
|
|
2043
|
-
apiKey;
|
|
2044
|
-
baseUrl;
|
|
2045
|
-
useEdge;
|
|
2046
|
-
timeout;
|
|
2047
|
-
debug;
|
|
2048
|
-
constructor(config) {
|
|
2049
|
-
if (!config.apiKey) {
|
|
2050
|
-
throw new Error("AgentShield API key is required");
|
|
2051
|
-
}
|
|
2052
|
-
this.apiKey = config.apiKey;
|
|
2053
|
-
this.useEdge = config.useEdge !== false;
|
|
2054
|
-
this.baseUrl = config.baseUrl || (this.useEdge ? EDGE_DETECT_URL : DEFAULT_BASE_URL);
|
|
2055
|
-
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
2056
|
-
this.debug = config.debug || false;
|
|
2057
|
-
}
|
|
2058
|
-
/**
|
|
2059
|
-
* Call the enforce API to check if a request should be allowed
|
|
2060
|
-
*/
|
|
2061
|
-
async enforce(input) {
|
|
2062
|
-
const startTime = Date.now();
|
|
2063
|
-
try {
|
|
2064
|
-
const controller = new AbortController();
|
|
2065
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2066
|
-
try {
|
|
2067
|
-
const endpoint = this.useEdge ? `${this.baseUrl}/__detect/enforce` : `${this.baseUrl}/api/v1/enforce`;
|
|
2068
|
-
const response = await fetch(endpoint, {
|
|
2069
|
-
method: "POST",
|
|
2070
|
-
headers: {
|
|
2071
|
-
"Content-Type": "application/json",
|
|
2072
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
2073
|
-
"X-Request-ID": input.requestId || crypto.randomUUID()
|
|
2074
|
-
},
|
|
2075
|
-
body: JSON.stringify(input),
|
|
2076
|
-
signal: controller.signal
|
|
2077
|
-
});
|
|
2078
|
-
clearTimeout(timeoutId);
|
|
2079
|
-
const data = await response.json();
|
|
2080
|
-
if (this.debug) {
|
|
2081
|
-
console.log("[AgentShield] Enforce response:", {
|
|
2082
|
-
status: response.status,
|
|
2083
|
-
action: data.data?.decision.action,
|
|
2084
|
-
processingTimeMs: Date.now() - startTime
|
|
2085
|
-
});
|
|
2086
|
-
}
|
|
2087
|
-
if (!response.ok) {
|
|
2088
|
-
return {
|
|
2089
|
-
success: false,
|
|
2090
|
-
error: {
|
|
2091
|
-
code: `HTTP_${response.status}`,
|
|
2092
|
-
message: data.error?.message || `HTTP error: ${response.status}`
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
return data;
|
|
2097
|
-
} catch (error) {
|
|
2098
|
-
clearTimeout(timeoutId);
|
|
2099
|
-
throw error;
|
|
2100
|
-
}
|
|
2101
|
-
} catch (error) {
|
|
2102
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2103
|
-
if (this.debug) {
|
|
2104
|
-
console.warn("[AgentShield] Request timed out");
|
|
2105
|
-
}
|
|
2106
|
-
return {
|
|
2107
|
-
success: false,
|
|
2108
|
-
error: {
|
|
2109
|
-
code: "TIMEOUT",
|
|
2110
|
-
message: `Request timed out after ${this.timeout}ms`
|
|
2111
|
-
}
|
|
2112
|
-
};
|
|
2113
|
-
}
|
|
2114
|
-
if (this.debug) {
|
|
2115
|
-
console.error("[AgentShield] Request failed:", error);
|
|
2116
|
-
}
|
|
2117
|
-
return {
|
|
2118
|
-
success: false,
|
|
2119
|
-
error: {
|
|
2120
|
-
code: "NETWORK_ERROR",
|
|
2121
|
-
message: error instanceof Error ? error.message : "Network request failed"
|
|
2122
|
-
}
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
/**
|
|
2127
|
-
* Quick check - returns just the action without full response parsing
|
|
2128
|
-
* Useful for very fast middleware that just needs allow/block
|
|
2129
|
-
*/
|
|
2130
|
-
async quickCheck(input) {
|
|
2131
|
-
const result = await this.enforce(input);
|
|
2132
|
-
if (!result.success || !result.data) {
|
|
2133
|
-
return {
|
|
2134
|
-
action: "allow",
|
|
2135
|
-
error: result.error?.message
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
return {
|
|
2139
|
-
action: result.data.decision.action
|
|
2140
|
-
};
|
|
2141
|
-
}
|
|
2142
|
-
/**
|
|
2143
|
-
* Check if this client is using edge detection (Gateway Worker)
|
|
2144
|
-
*/
|
|
2145
|
-
isUsingEdge() {
|
|
2146
|
-
return this.useEdge;
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Log a detection result to AgentShield database.
|
|
2150
|
-
* Use after Gateway Worker detection to persist results.
|
|
2151
|
-
* Fire-and-forget - returns immediately without waiting for DB write.
|
|
2152
|
-
*
|
|
2153
|
-
* @example
|
|
2154
|
-
* ```typescript
|
|
2155
|
-
* // After receiving Gateway response
|
|
2156
|
-
* if (client.isUsingEdge() && response.data?.detection) {
|
|
2157
|
-
* client.logDetection({
|
|
2158
|
-
* detection: response.data.detection,
|
|
2159
|
-
* context: { userAgent, ipAddress, path, url, method }
|
|
2160
|
-
* }).catch(err => console.error('Log failed:', err));
|
|
2161
|
-
* }
|
|
2162
|
-
* ```
|
|
2163
|
-
*/
|
|
2164
|
-
async logDetection(input) {
|
|
2165
|
-
const logEndpoint = this.useEdge ? `${DEFAULT_BASE_URL}/api/v1/log-detection` : `${this.baseUrl}/api/v1/log-detection`;
|
|
2166
|
-
try {
|
|
2167
|
-
const controller = new AbortController();
|
|
2168
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2169
|
-
try {
|
|
2170
|
-
const response = await fetch(logEndpoint, {
|
|
2171
|
-
method: "POST",
|
|
2172
|
-
headers: {
|
|
2173
|
-
"Content-Type": "application/json",
|
|
2174
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
2175
|
-
},
|
|
2176
|
-
body: JSON.stringify({
|
|
2177
|
-
detection: {
|
|
2178
|
-
isAgent: input.detection.isAgent,
|
|
2179
|
-
confidence: input.detection.confidence,
|
|
2180
|
-
agentName: input.detection.agentName,
|
|
2181
|
-
agentType: input.detection.agentType,
|
|
2182
|
-
detectionClass: input.detection.detectionClass,
|
|
2183
|
-
verificationMethod: input.detection.verificationMethod,
|
|
2184
|
-
reasons: input.detection.reasons
|
|
2185
|
-
},
|
|
2186
|
-
context: input.context,
|
|
2187
|
-
source: input.source || "gateway"
|
|
2188
|
-
}),
|
|
2189
|
-
signal: controller.signal
|
|
2190
|
-
});
|
|
2191
|
-
clearTimeout(timeoutId);
|
|
2192
|
-
if (!response.ok && this.debug) {
|
|
2193
|
-
console.warn("[AgentShield] Log detection returned non-2xx:", response.status);
|
|
2194
|
-
}
|
|
2195
|
-
} catch (error) {
|
|
2196
|
-
clearTimeout(timeoutId);
|
|
2197
|
-
throw error;
|
|
2198
|
-
}
|
|
2199
|
-
} catch (error) {
|
|
2200
|
-
if (this.debug) {
|
|
2201
|
-
console.error("[AgentShield] Log detection failed:", error);
|
|
2202
|
-
}
|
|
2203
|
-
throw error;
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
};
|
|
2207
|
-
var clientInstance = null;
|
|
2208
|
-
function getAgentShieldClient(config) {
|
|
2209
|
-
if (!clientInstance) {
|
|
2210
|
-
const apiKey = config?.apiKey || process.env.AGENTSHIELD_API_KEY;
|
|
2211
|
-
if (!apiKey) {
|
|
2212
|
-
throw new Error(
|
|
2213
|
-
"AgentShield API key is required. Set AGENTSHIELD_API_KEY environment variable or pass apiKey in config."
|
|
2214
|
-
);
|
|
2215
|
-
}
|
|
2216
|
-
clientInstance = new AgentShieldClient({
|
|
2217
|
-
apiKey,
|
|
2218
|
-
baseUrl: config?.baseUrl || process.env.AGENTSHIELD_API_URL,
|
|
2219
|
-
// Default to edge detection unless explicitly disabled
|
|
2220
|
-
useEdge: config?.useEdge ?? process.env.AGENTSHIELD_USE_EDGE !== "false",
|
|
2221
|
-
timeout: config?.timeout,
|
|
2222
|
-
debug: config?.debug || process.env.AGENTSHIELD_DEBUG === "true"
|
|
2223
|
-
});
|
|
2224
|
-
}
|
|
2225
|
-
return clientInstance;
|
|
2226
|
-
}
|
|
2227
|
-
function resetAgentShieldClient() {
|
|
2228
|
-
clientInstance = null;
|
|
2229
|
-
}
|
|
2230
|
-
var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
|
|
2231
|
-
var DEFAULT_CONNECT_PATH = "/connect";
|
|
2232
|
-
function buildAgentInstructionResponse(request, decision, redirectUrl) {
|
|
2233
|
-
const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
|
|
2234
|
-
const agentName = decision.agentName || decision.agentType || "unknown";
|
|
2235
|
-
if (!resolved.searchParams.has("agent")) {
|
|
2236
|
-
resolved.searchParams.set("agent", agentName.toLowerCase());
|
|
2237
|
-
}
|
|
2238
|
-
const authUrl = resolved.toString();
|
|
2239
|
-
const hostname = safeHostname(request.url);
|
|
2240
|
-
const body = {
|
|
2241
|
-
// Markdown-formatted so clients that render markdown (Claude Desktop,
|
|
2242
|
-
// ChatGPT web) surface the URL as a clickable link. Tone mirrors the
|
|
2243
|
-
// gateway response so messaging stays consistent across platforms.
|
|
2244
|
-
message: `I can't access ${hostname} yet \u2014 this site checks AI assistants at the front door.
|
|
2245
|
-
|
|
2246
|
-
**To give me access, open this link:**
|
|
2247
|
-
[Connect securely to ${hostname}](${authUrl})
|
|
2248
|
-
|
|
2249
|
-
It only takes a moment and you won't need to do it again. Once you're done, ask me to try again and I'll connect through the verified channel automatically.`,
|
|
2250
|
-
user_action_required: {
|
|
2251
|
-
action: `Connect securely to ${hostname}`,
|
|
2252
|
-
url: authUrl,
|
|
2253
|
-
reason: `${hostname} checks AI assistants before they connect. Open the link to give your assistant a verified key.`
|
|
2254
|
-
},
|
|
2255
|
-
mcp_i: {
|
|
2256
|
-
version: "1.0",
|
|
2257
|
-
action: "authenticate",
|
|
2258
|
-
authorization_url: authUrl,
|
|
2259
|
-
flow: {
|
|
2260
|
-
type: "oauth2_delegation",
|
|
2261
|
-
steps: [
|
|
2262
|
-
"1. Direct your user to the authorization_url",
|
|
2263
|
-
"2. User reviews requested scopes and grants consent",
|
|
2264
|
-
"3. Receive delegation credential (JWT)",
|
|
2265
|
-
"4. Include credential in KYA-Delegation header",
|
|
2266
|
-
"5. Retry this request with the proof"
|
|
2267
|
-
]
|
|
2268
|
-
},
|
|
2269
|
-
retry_instructions: {
|
|
2270
|
-
header: "KYA-Delegation",
|
|
2271
|
-
format: "JWT delegation credential from authorization flow"
|
|
2272
|
-
},
|
|
2273
|
-
documentation: MCP_I_DOCS_URL
|
|
2274
|
-
},
|
|
2275
|
-
error: "mcp_authentication_required",
|
|
2276
|
-
code: "AGENT_REQUIRES_DELEGATION",
|
|
2277
|
-
detection: {
|
|
2278
|
-
agent_type: decision.agentType || "ai_agent",
|
|
2279
|
-
agent_name: decision.agentName || "Unknown Agent",
|
|
2280
|
-
confidence: decision.confidence
|
|
2281
|
-
}
|
|
2282
|
-
};
|
|
2283
|
-
const response = server.NextResponse.json(body, { status: 401 });
|
|
2284
|
-
response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
|
|
2285
|
-
response.headers.set(
|
|
2286
|
-
"Link",
|
|
2287
|
-
`<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
|
|
2288
|
-
);
|
|
2289
|
-
response.headers.set("KYA-Auth-Required", "true");
|
|
2290
|
-
response.headers.set("KYA-Auth-Url", authUrl);
|
|
2291
|
-
response.headers.set("KYA-Action", "instruct");
|
|
2292
|
-
response.headers.set("KYA-Detected-Agent", agentName);
|
|
2293
|
-
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2294
|
-
response.headers.set("Cache-Control", "no-store");
|
|
2295
|
-
return response;
|
|
2296
|
-
}
|
|
2297
|
-
function resolveUrl(target, baseUrl2) {
|
|
2298
|
-
try {
|
|
2299
|
-
return new URL(target, baseUrl2);
|
|
2300
|
-
} catch {
|
|
2301
|
-
return new URL(DEFAULT_CONNECT_PATH, baseUrl2);
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
// src/api-middleware.ts
|
|
2306
|
-
function matchPath(path, pattern) {
|
|
2307
|
-
if (pattern === path) return true;
|
|
2308
|
-
if (pattern.includes("*")) {
|
|
2309
|
-
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
2310
|
-
return new RegExp(`^${regexPattern}$`).test(path);
|
|
2311
|
-
}
|
|
2312
|
-
if (pattern.endsWith("/")) {
|
|
2313
|
-
return path.startsWith(pattern) || path === pattern.slice(0, -1);
|
|
2314
|
-
}
|
|
2315
|
-
return path.startsWith(pattern);
|
|
2316
|
-
}
|
|
2317
|
-
function shouldSkipPath(path, skipPaths) {
|
|
2318
|
-
return skipPaths.some((pattern) => matchPath(path, pattern));
|
|
2319
|
-
}
|
|
2320
|
-
function shouldIncludePath(path, includePaths) {
|
|
2321
|
-
if (!includePaths || includePaths.length === 0) return true;
|
|
2322
|
-
return includePaths.some((pattern) => matchPath(path, pattern));
|
|
2323
|
-
}
|
|
2324
|
-
function buildBlockedResponse(request, decision, config) {
|
|
2325
|
-
const status = config.blockedResponse?.status ?? 403;
|
|
2326
|
-
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2327
|
-
const recoveryUrl = resolveRecoveryUrl(request, config.redirectUrl || decision.redirectUrl);
|
|
2328
|
-
const body = {
|
|
2329
|
-
error: message,
|
|
2330
|
-
code: "AGENT_BLOCKED",
|
|
2331
|
-
reason: decision.reason,
|
|
2332
|
-
agentType: decision.agentType
|
|
2333
|
-
};
|
|
2334
|
-
if (recoveryUrl) {
|
|
2335
|
-
const hostname = safeHostname(request.url);
|
|
2336
|
-
body.user_action_required = {
|
|
2337
|
-
action: `Connect securely to ${hostname}`,
|
|
2338
|
-
url: recoveryUrl,
|
|
2339
|
-
reason: `${hostname} blocks unverified AI assistants. Open the link to give your assistant a verified key and try again.`
|
|
2340
|
-
};
|
|
2341
|
-
body.message = `I can't access ${hostname} \u2014 this site blocks unverified AI assistants.
|
|
2342
|
-
|
|
2343
|
-
**To give me access, open this link:**
|
|
2344
|
-
[Connect securely to ${hostname}](${recoveryUrl})
|
|
2345
|
-
|
|
2346
|
-
Once you're done, ask me to try again.`;
|
|
2347
|
-
}
|
|
2348
|
-
const response = server.NextResponse.json(body, { status });
|
|
2349
|
-
if (config.blockedResponse?.headers) {
|
|
2350
|
-
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2351
|
-
response.headers.set(key, value);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
if (recoveryUrl) {
|
|
2355
|
-
response.headers.set("Link", `<${recoveryUrl}>; rel="kya-authorize"`);
|
|
2356
|
-
response.headers.set("KYA-Auth-Url", recoveryUrl);
|
|
2357
|
-
}
|
|
2358
|
-
response.headers.set("KYA-Action", decision.action);
|
|
2359
|
-
response.headers.set("KYA-Reason", decision.reason);
|
|
2360
|
-
return response;
|
|
2361
|
-
}
|
|
2362
|
-
function resolveRecoveryUrl(request, target) {
|
|
2363
|
-
if (!target) return void 0;
|
|
2364
|
-
try {
|
|
2365
|
-
return new URL(target, request.url).toString();
|
|
2366
|
-
} catch {
|
|
2367
|
-
return void 0;
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
function buildRedirectResponse(request, decision, config) {
|
|
2371
|
-
const redirectUrl = config.redirectUrl || decision.redirectUrl || "/blocked";
|
|
2372
|
-
const url = new URL(redirectUrl, request.url);
|
|
2373
|
-
url.searchParams.set("reason", decision.reason);
|
|
2374
|
-
if (decision.agentType) {
|
|
2375
|
-
url.searchParams.set("agent", decision.agentType);
|
|
2376
|
-
}
|
|
2377
|
-
return server.NextResponse.redirect(url);
|
|
2378
|
-
}
|
|
2379
|
-
function withAgentShield(config = {}) {
|
|
2380
|
-
let client = null;
|
|
2381
|
-
const getClient = () => {
|
|
2382
|
-
if (!client) {
|
|
2383
|
-
client = getAgentShieldClient({
|
|
2384
|
-
apiKey: config.apiKey,
|
|
2385
|
-
baseUrl: config.apiUrl,
|
|
2386
|
-
useEdge: config.useEdge,
|
|
2387
|
-
timeout: config.timeout,
|
|
2388
|
-
debug: config.debug
|
|
2389
|
-
});
|
|
2390
|
-
}
|
|
2391
|
-
return client;
|
|
2392
|
-
};
|
|
2393
|
-
const defaultSkipPaths = [
|
|
2394
|
-
"/_next/static/*",
|
|
2395
|
-
"/_next/image/*",
|
|
2396
|
-
"/favicon.ico",
|
|
2397
|
-
"/robots.txt",
|
|
2398
|
-
"/sitemap.xml"
|
|
2399
|
-
];
|
|
2400
|
-
const skipPaths = [...defaultSkipPaths, ...config.skipPaths || []];
|
|
2401
|
-
const failOpen = config.failOpen ?? true;
|
|
2402
|
-
return async function middleware(request) {
|
|
2403
|
-
const path = request.nextUrl.pathname;
|
|
2404
|
-
const startTime = Date.now();
|
|
2405
|
-
if (shouldSkipPath(path, skipPaths)) {
|
|
2406
|
-
return server.NextResponse.next();
|
|
2407
|
-
}
|
|
2408
|
-
if (!shouldIncludePath(path, config.includePaths)) {
|
|
2409
|
-
return server.NextResponse.next();
|
|
2410
|
-
}
|
|
2411
|
-
try {
|
|
2412
|
-
const client2 = getClient();
|
|
2413
|
-
const userAgent = request.headers.get("user-agent") || void 0;
|
|
2414
|
-
const ipAddress = getClientIp(request);
|
|
2415
|
-
const result = await client2.enforce({
|
|
2416
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
2417
|
-
userAgent,
|
|
2418
|
-
ipAddress,
|
|
2419
|
-
path,
|
|
2420
|
-
url: request.url,
|
|
2421
|
-
method: request.method,
|
|
2422
|
-
requestId: request.headers.get("x-request-id") || void 0,
|
|
2423
|
-
options: {
|
|
2424
|
-
// Always include detection results for logging (needed when using edge)
|
|
2425
|
-
includeDetectionResult: true
|
|
2426
|
-
}
|
|
2427
|
-
});
|
|
2428
|
-
if (!result.success || !result.data) {
|
|
2429
|
-
if (config.debug) {
|
|
2430
|
-
console.warn("[AgentShield] API error:", result.error);
|
|
2431
|
-
}
|
|
2432
|
-
if (failOpen) {
|
|
2433
|
-
return server.NextResponse.next();
|
|
2434
|
-
}
|
|
2435
|
-
return server.NextResponse.json(
|
|
2436
|
-
{ error: "Security check failed", code: "API_ERROR" },
|
|
2437
|
-
{ status: 503 }
|
|
2438
|
-
);
|
|
2439
|
-
}
|
|
2440
|
-
const decision = result.data.decision;
|
|
2441
|
-
if (config.debug) {
|
|
2442
|
-
console.log("[AgentShield] Decision:", {
|
|
2443
|
-
path,
|
|
2444
|
-
action: decision.action,
|
|
2445
|
-
isAgent: decision.isAgent,
|
|
2446
|
-
confidence: decision.confidence,
|
|
2447
|
-
agentName: decision.agentName,
|
|
2448
|
-
detectionMethod: result.data.detection?.detectionMethod || "not-included",
|
|
2449
|
-
processingTimeMs: Date.now() - startTime
|
|
2450
|
-
});
|
|
2451
|
-
}
|
|
2452
|
-
if (client2.isUsingEdge() && result.data.detection) {
|
|
2453
|
-
client2.logDetection({
|
|
2454
|
-
detection: result.data.detection,
|
|
2455
|
-
context: { userAgent, ipAddress, path, url: request.url, method: request.method }
|
|
2456
|
-
}).catch((err) => {
|
|
2457
|
-
if (config.debug) {
|
|
2458
|
-
console.error("[AgentShield] Log detection failed:", err);
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2461
|
-
}
|
|
2462
|
-
if (decision.isAgent && config.onAgentDetected) {
|
|
2463
|
-
await config.onAgentDetected(request, decision);
|
|
2464
|
-
}
|
|
2465
|
-
const redirectMode = config.redirectMode ?? "instruct";
|
|
2466
|
-
switch (decision.action) {
|
|
2467
|
-
case "block": {
|
|
2468
|
-
if (config.customBlockedResponse) {
|
|
2469
|
-
return config.customBlockedResponse(request, decision);
|
|
2470
|
-
}
|
|
2471
|
-
if (config.onBlock === "redirect") {
|
|
2472
|
-
return buildRedirectResponse(request, decision, config);
|
|
2473
|
-
}
|
|
2474
|
-
return buildBlockedResponse(request, decision, config);
|
|
2475
|
-
}
|
|
2476
|
-
case "redirect":
|
|
2477
|
-
case "instruct": {
|
|
2478
|
-
if (redirectMode === "http" && decision.action === "redirect") {
|
|
2479
|
-
return buildRedirectResponse(request, decision, config);
|
|
2480
|
-
}
|
|
2481
|
-
const targetUrl = config.redirectUrl || decision.redirectUrl;
|
|
2482
|
-
return buildAgentInstructionResponse(request, decision, targetUrl);
|
|
2483
|
-
}
|
|
2484
|
-
case "challenge": {
|
|
2485
|
-
return buildRedirectResponse(request, decision, config);
|
|
2486
|
-
}
|
|
2487
|
-
case "log":
|
|
2488
|
-
case "allow":
|
|
2489
|
-
default: {
|
|
2490
|
-
const response = server.NextResponse.next();
|
|
2491
|
-
if (decision.isAgent) {
|
|
2492
|
-
response.headers.set("KYA-Detected", "true");
|
|
2493
|
-
response.headers.set("KYA-Confidence", decision.confidence.toString());
|
|
2494
|
-
if (decision.agentName) {
|
|
2495
|
-
response.headers.set("KYA-Agent", decision.agentName);
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
return response;
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
} catch (error) {
|
|
2502
|
-
if (config.debug) {
|
|
2503
|
-
console.error("[AgentShield] Middleware error:", error);
|
|
2504
|
-
}
|
|
2505
|
-
if (failOpen) {
|
|
2506
|
-
return server.NextResponse.next();
|
|
2507
|
-
}
|
|
2508
|
-
return server.NextResponse.json(
|
|
2509
|
-
{ error: "Security check failed", code: "MIDDLEWARE_ERROR" },
|
|
2510
|
-
{ status: 503 }
|
|
2511
|
-
);
|
|
2512
|
-
}
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
var agentShieldMiddleware = withAgentShield();
|
|
2516
|
-
function createContextFromDetection(detection, request) {
|
|
2517
|
-
return agentshieldShared.createEvaluationContext({
|
|
2518
|
-
agentType: detection.detectedAgent?.type,
|
|
2519
|
-
agentName: detection.detectedAgent?.name,
|
|
2520
|
-
agentVendor: detection.detectedAgent?.vendor,
|
|
2521
|
-
confidence: detection.confidence,
|
|
2522
|
-
riskLevel: detection.riskLevel,
|
|
2523
|
-
path: request.nextUrl.pathname,
|
|
2524
|
-
method: request.method,
|
|
2525
|
-
signatureVerified: detection.verificationMethod === "signature",
|
|
2526
|
-
isAuthenticated: false,
|
|
2527
|
-
// TODO: integrate with auth
|
|
2528
|
-
userAgent: request.headers.get("user-agent") || void 0
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
function evaluatePolicyForDetection(detection, request, policy) {
|
|
2532
|
-
const context = createContextFromDetection(detection, request);
|
|
2533
|
-
return agentshieldShared.evaluatePolicy(policy, context);
|
|
2534
|
-
}
|
|
2535
|
-
function buildBlockedResponse2(decision, config) {
|
|
2536
|
-
const status = config.blockedResponse?.status ?? 403;
|
|
2537
|
-
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
|
|
2538
|
-
const response = server.NextResponse.json(
|
|
2539
|
-
{
|
|
2540
|
-
error: message,
|
|
2541
|
-
code: "POLICY_BLOCKED",
|
|
2542
|
-
reason: decision.reason,
|
|
2543
|
-
ruleId: decision.ruleId,
|
|
2544
|
-
matchType: decision.matchType
|
|
2545
|
-
},
|
|
2546
|
-
{ status }
|
|
2547
|
-
);
|
|
2548
|
-
if (config.blockedResponse?.headers) {
|
|
2549
|
-
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
|
|
2550
|
-
response.headers.set(key, value);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
response.headers.set("KYA-Action", decision.action);
|
|
2554
|
-
response.headers.set("KYA-Reason", decision.reason);
|
|
2555
|
-
response.headers.set("KYA-Match-Type", decision.matchType);
|
|
2556
|
-
return response;
|
|
2557
|
-
}
|
|
2558
|
-
function buildRedirectResponse2(request, decision, config, detection) {
|
|
2559
|
-
const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
|
|
2560
|
-
const url = new URL(redirectUrl, request.url);
|
|
2561
|
-
url.searchParams.set("reason", decision.reason);
|
|
2562
|
-
if (decision.ruleId) {
|
|
2563
|
-
url.searchParams.set("ruleId", decision.ruleId);
|
|
2564
|
-
}
|
|
2565
|
-
const agentName = detection?.detectedAgent?.name;
|
|
2566
|
-
if (agentName && !url.searchParams.has("agent")) {
|
|
2567
|
-
url.searchParams.set("agent", agentName.toLowerCase());
|
|
2568
|
-
}
|
|
2569
|
-
return server.NextResponse.redirect(url);
|
|
2570
|
-
}
|
|
2571
|
-
function buildChallengeResponse(request, decision, config, detection) {
|
|
2572
|
-
return buildRedirectResponse2(request, decision, config, detection);
|
|
2573
|
-
}
|
|
2574
|
-
async function handlePolicyDecision(request, decision, config, detection) {
|
|
2575
|
-
switch (decision.action) {
|
|
2576
|
-
case agentshieldShared.ENFORCEMENT_ACTIONS.BLOCK:
|
|
2577
|
-
if (config.customBlockedResponse) {
|
|
2578
|
-
return await config.customBlockedResponse(request, decision);
|
|
2579
|
-
}
|
|
2580
|
-
return buildBlockedResponse2(decision, config);
|
|
2581
|
-
case agentshieldShared.ENFORCEMENT_ACTIONS.REDIRECT:
|
|
2582
|
-
return buildRedirectResponse2(request, decision, config, detection);
|
|
2583
|
-
case agentshieldShared.ENFORCEMENT_ACTIONS.CHALLENGE:
|
|
2584
|
-
return buildChallengeResponse(request, decision, config, detection);
|
|
2585
|
-
case agentshieldShared.ENFORCEMENT_ACTIONS.LOG:
|
|
2586
|
-
console.log("[AgentShield] Policy decision (log):", {
|
|
2587
|
-
path: request.nextUrl.pathname,
|
|
2588
|
-
action: decision.action,
|
|
2589
|
-
reason: decision.reason,
|
|
2590
|
-
matchType: decision.matchType,
|
|
2591
|
-
ruleId: decision.ruleId
|
|
2592
|
-
});
|
|
2593
|
-
return null;
|
|
2594
|
-
// Continue to allow
|
|
2595
|
-
case agentshieldShared.ENFORCEMENT_ACTIONS.ALLOW:
|
|
2596
|
-
default:
|
|
2597
|
-
return null;
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
var fetcherCache = /* @__PURE__ */ new Map();
|
|
2601
|
-
function getFetcherCacheKey(config) {
|
|
2602
|
-
return `${config.apiUrl ?? "default"}:${config.apiKey ?? ""}:${config.cacheTtlSeconds ?? "default"}`;
|
|
2603
|
-
}
|
|
2604
|
-
function getPolicyFetcher(config) {
|
|
2605
|
-
if (!config) {
|
|
2606
|
-
throw new Error("fetchPolicy config required");
|
|
2607
|
-
}
|
|
2608
|
-
const cacheKey = getFetcherCacheKey(config);
|
|
2609
|
-
let fetcher = fetcherCache.get(cacheKey);
|
|
2610
|
-
if (!fetcher) {
|
|
2611
|
-
const fetcherConfig = {
|
|
2612
|
-
apiBaseUrl: config.apiUrl || "https://kya.vouched.id",
|
|
2613
|
-
apiKey: config.apiKey,
|
|
2614
|
-
cacheTtlSeconds: config.cacheTtlSeconds
|
|
2615
|
-
};
|
|
2616
|
-
fetcher = agentshieldShared.createPolicyFetcher(fetcherConfig);
|
|
2617
|
-
fetcherCache.set(cacheKey, fetcher);
|
|
2618
|
-
}
|
|
2619
|
-
return fetcher;
|
|
2620
|
-
}
|
|
2621
|
-
async function getPolicy(config) {
|
|
2622
|
-
if (config.policy) {
|
|
2623
|
-
return agentshieldShared.PolicyConfigSchema.parse({ ...agentshieldShared.DEFAULT_POLICY, ...config.policy });
|
|
2624
|
-
}
|
|
2625
|
-
if (config.fetchPolicy) {
|
|
2626
|
-
try {
|
|
2627
|
-
const fetcher = getPolicyFetcher(config.fetchPolicy);
|
|
2628
|
-
return await fetcher.getPolicy(config.fetchPolicy.projectId);
|
|
2629
|
-
} catch (error) {
|
|
2630
|
-
if (config.debug) {
|
|
2631
|
-
console.warn("[AgentShield] Policy fetch failed, using fallback:", error);
|
|
2632
|
-
}
|
|
2633
|
-
return agentshieldShared.PolicyConfigSchema.parse({
|
|
2634
|
-
...agentshieldShared.DEFAULT_POLICY,
|
|
2635
|
-
...config.fallbackPolicy || {}
|
|
2636
|
-
});
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
return agentshieldShared.PolicyConfigSchema.parse(agentshieldShared.DEFAULT_POLICY);
|
|
2640
|
-
}
|
|
2641
|
-
async function applyPolicy(request, detection, config) {
|
|
2642
|
-
try {
|
|
2643
|
-
const path = request.nextUrl.pathname;
|
|
2644
|
-
if (config.skipPaths?.some((pattern) => agentshieldShared.matchPath(path, pattern))) {
|
|
2645
|
-
return null;
|
|
2646
|
-
}
|
|
2647
|
-
if (config.includePaths && config.includePaths.length > 0) {
|
|
2648
|
-
if (!config.includePaths.some((pattern) => agentshieldShared.matchPath(path, pattern))) {
|
|
2649
|
-
return null;
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
const policy = await getPolicy(config);
|
|
2653
|
-
const context = createContextFromDetection(detection, request);
|
|
2654
|
-
const decision = agentshieldShared.evaluatePolicy(policy, context);
|
|
2655
|
-
if (config.onPolicyDecision) {
|
|
2656
|
-
await config.onPolicyDecision(request, decision, context);
|
|
2657
|
-
}
|
|
2658
|
-
return await handlePolicyDecision(request, decision, config, detection);
|
|
2659
|
-
} catch (error) {
|
|
2660
|
-
if (config.debug) {
|
|
2661
|
-
console.error("[AgentShield] Policy evaluation error:", error);
|
|
2662
|
-
}
|
|
2663
|
-
if (config.failOpen !== false) {
|
|
2664
|
-
return null;
|
|
2665
|
-
}
|
|
2666
|
-
return server.NextResponse.json(
|
|
2667
|
-
{ error: "Security check failed", code: "POLICY_ERROR" },
|
|
2668
|
-
{ status: 503 }
|
|
2669
|
-
);
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
// src/index.ts
|
|
2674
|
-
var VERSION = "0.1.0";
|
|
2675
|
-
/**
|
|
2676
|
-
* @fileoverview AgentShield Next.js Integration
|
|
2677
|
-
* @version 0.1.0
|
|
2678
|
-
* @license MIT OR Apache-2.0
|
|
2679
|
-
*/
|
|
2680
|
-
|
|
2681
|
-
Object.defineProperty(exports, "DEFAULT_POLICY", {
|
|
2682
|
-
enumerable: true,
|
|
2683
|
-
get: function () { return agentshieldShared.DEFAULT_POLICY; }
|
|
2684
|
-
});
|
|
2685
|
-
Object.defineProperty(exports, "ENFORCEMENT_ACTIONS", {
|
|
2686
|
-
enumerable: true,
|
|
2687
|
-
get: function () { return agentshieldShared.ENFORCEMENT_ACTIONS; }
|
|
2688
|
-
});
|
|
2689
|
-
Object.defineProperty(exports, "createEvaluationContext", {
|
|
2690
|
-
enumerable: true,
|
|
2691
|
-
get: function () { return agentshieldShared.createEvaluationContext; }
|
|
2692
|
-
});
|
|
2693
|
-
Object.defineProperty(exports, "evaluatePolicy", {
|
|
2694
|
-
enumerable: true,
|
|
2695
|
-
get: function () { return agentshieldShared.evaluatePolicy; }
|
|
2696
|
-
});
|
|
2697
|
-
exports.AgentShieldClient = AgentShieldClient;
|
|
2698
|
-
exports.EdgeSessionTracker = EdgeSessionTracker;
|
|
2699
|
-
exports.StatelessSessionChecker = StatelessSessionChecker;
|
|
2700
|
-
exports.VERSION = VERSION;
|
|
2701
|
-
exports.agentShieldMiddleware = agentShieldMiddleware;
|
|
2702
|
-
exports.applyPolicy = applyPolicy;
|
|
2703
|
-
exports.buildPolicyBlockedResponse = buildBlockedResponse2;
|
|
2704
|
-
exports.buildPolicyRedirectResponse = buildRedirectResponse2;
|
|
2705
|
-
exports.createAgentShieldMiddleware = createAgentShieldMiddleware2;
|
|
2706
|
-
exports.createAgentShieldMiddlewareBase = createAgentShieldMiddleware;
|
|
2707
|
-
exports.createContextFromDetection = createContextFromDetection;
|
|
2708
|
-
exports.createEnhancedAgentShieldMiddleware = createEnhancedAgentShieldMiddleware;
|
|
2709
|
-
exports.createMiddleware = createAgentShieldMiddleware2;
|
|
2710
|
-
exports.evaluatePolicyForDetection = evaluatePolicyForDetection;
|
|
2711
|
-
exports.getAgentShieldClient = getAgentShieldClient;
|
|
2712
|
-
exports.getPolicy = getPolicy;
|
|
2713
|
-
exports.handlePolicyDecision = handlePolicyDecision;
|
|
2714
|
-
exports.resetAgentShieldClient = resetAgentShieldClient;
|
|
2715
|
-
exports.withAgentShield = withAgentShield;
|
|
2716
|
-
//# sourceMappingURL=index.js.map
|
|
2717
|
-
//# sourceMappingURL=index.js.map
|