@kya-os/agentshield-nextjs 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-middleware.js +283 -13
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +283 -13
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge-detector-wrapper.d.mts +1 -0
- package/dist/edge-detector-wrapper.d.ts +1 -0
- package/dist/edge-detector-wrapper.js +251 -12
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +251 -12
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/index.js +283 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +283 -13
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +283 -13
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +283 -13
- package/dist/middleware.mjs.map +1 -1
- package/dist/signature-verifier.d.mts +32 -0
- package/dist/signature-verifier.d.ts +32 -0
- package/dist/signature-verifier.js +220 -0
- package/dist/signature-verifier.js.map +1 -0
- package/dist/signature-verifier.mjs +216 -0
- package/dist/signature-verifier.mjs.map +1 -0
- package/package.json +2 -2
package/dist/middleware.mjs
CHANGED
|
@@ -2,6 +2,219 @@ import { NextResponse } from 'next/server';
|
|
|
2
2
|
|
|
3
3
|
// src/middleware.ts
|
|
4
4
|
|
|
5
|
+
// src/signature-verifier.ts
|
|
6
|
+
var KNOWN_KEYS = {
|
|
7
|
+
chatgpt: [
|
|
8
|
+
{
|
|
9
|
+
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
10
|
+
// ChatGPT's current Ed25519 public key (base64)
|
|
11
|
+
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
12
|
+
validFrom: (/* @__PURE__ */ new Date("2025-01-01")).getTime() / 1e3,
|
|
13
|
+
validUntil: (/* @__PURE__ */ new Date("2025-04-11")).getTime() / 1e3
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
function parseSignatureInput(signatureInput) {
|
|
18
|
+
try {
|
|
19
|
+
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
20
|
+
if (!match) return null;
|
|
21
|
+
const [, headersList, params] = match;
|
|
22
|
+
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
23
|
+
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
24
|
+
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
25
|
+
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
26
|
+
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
27
|
+
return {
|
|
28
|
+
keyid: keyidMatch[1],
|
|
29
|
+
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
30
|
+
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
31
|
+
signedHeaders
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
39
|
+
const components = [];
|
|
40
|
+
for (const headerName of signedHeaders) {
|
|
41
|
+
let value;
|
|
42
|
+
switch (headerName) {
|
|
43
|
+
case "@method":
|
|
44
|
+
value = method.toUpperCase();
|
|
45
|
+
break;
|
|
46
|
+
case "@path":
|
|
47
|
+
value = path;
|
|
48
|
+
break;
|
|
49
|
+
case "@authority":
|
|
50
|
+
value = headers["host"] || headers["Host"] || "";
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
const key = Object.keys(headers).find(
|
|
54
|
+
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
55
|
+
);
|
|
56
|
+
value = key ? headers[key] || "" : "";
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
components.push(`"${headerName}": ${value}`);
|
|
60
|
+
}
|
|
61
|
+
return components.join("\n");
|
|
62
|
+
}
|
|
63
|
+
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
64
|
+
try {
|
|
65
|
+
const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
|
|
66
|
+
const signatureBytes = Uint8Array.from(atob(signatureBase64), (c) => c.charCodeAt(0));
|
|
67
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
68
|
+
if (publicKeyBytes.length !== 32) {
|
|
69
|
+
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (signatureBytes.length !== 64) {
|
|
73
|
+
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const publicKey = await crypto.subtle.importKey(
|
|
77
|
+
"raw",
|
|
78
|
+
publicKeyBytes,
|
|
79
|
+
{
|
|
80
|
+
name: "Ed25519",
|
|
81
|
+
namedCurve: "Ed25519"
|
|
82
|
+
},
|
|
83
|
+
false,
|
|
84
|
+
["verify"]
|
|
85
|
+
);
|
|
86
|
+
const isValid = await crypto.subtle.verify(
|
|
87
|
+
"Ed25519",
|
|
88
|
+
publicKey,
|
|
89
|
+
signatureBytes,
|
|
90
|
+
messageBytes
|
|
91
|
+
);
|
|
92
|
+
return isValid;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("[Signature] Ed25519 verification failed:", error);
|
|
95
|
+
if (typeof window === "undefined") {
|
|
96
|
+
try {
|
|
97
|
+
console.warn("[Signature] Ed25519 not supported in this environment");
|
|
98
|
+
return false;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function verifyAgentSignature(method, path, headers) {
|
|
107
|
+
const signature = headers["signature"] || headers["Signature"];
|
|
108
|
+
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
109
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
110
|
+
if (!signature || !signatureInput) {
|
|
111
|
+
return {
|
|
112
|
+
isValid: false,
|
|
113
|
+
confidence: 0,
|
|
114
|
+
reason: "No signature headers present",
|
|
115
|
+
verificationMethod: "none"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const parsed = parseSignatureInput(signatureInput);
|
|
119
|
+
if (!parsed) {
|
|
120
|
+
return {
|
|
121
|
+
isValid: false,
|
|
122
|
+
confidence: 0,
|
|
123
|
+
reason: "Invalid Signature-Input header",
|
|
124
|
+
verificationMethod: "none"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (parsed.created) {
|
|
128
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
129
|
+
const age = now2 - parsed.created;
|
|
130
|
+
if (age > 300) {
|
|
131
|
+
return {
|
|
132
|
+
isValid: false,
|
|
133
|
+
confidence: 0,
|
|
134
|
+
reason: "Signature expired (older than 5 minutes)",
|
|
135
|
+
verificationMethod: "none"
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (age < -30) {
|
|
139
|
+
return {
|
|
140
|
+
isValid: false,
|
|
141
|
+
confidence: 0,
|
|
142
|
+
reason: "Signature timestamp is in the future",
|
|
143
|
+
verificationMethod: "none"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let agent;
|
|
148
|
+
let knownKeys;
|
|
149
|
+
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
150
|
+
agent = "ChatGPT";
|
|
151
|
+
knownKeys = KNOWN_KEYS.chatgpt;
|
|
152
|
+
}
|
|
153
|
+
if (!agent || !knownKeys) {
|
|
154
|
+
return {
|
|
155
|
+
isValid: false,
|
|
156
|
+
confidence: 0,
|
|
157
|
+
reason: "Unknown signature agent",
|
|
158
|
+
verificationMethod: "none"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
162
|
+
if (!key) {
|
|
163
|
+
return {
|
|
164
|
+
isValid: false,
|
|
165
|
+
confidence: 0,
|
|
166
|
+
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
167
|
+
verificationMethod: "none"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
171
|
+
if (now < key.validFrom || now > key.validUntil) {
|
|
172
|
+
return {
|
|
173
|
+
isValid: false,
|
|
174
|
+
confidence: 0,
|
|
175
|
+
reason: "Key is not valid at current time",
|
|
176
|
+
verificationMethod: "none"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
180
|
+
let signatureValue = signature;
|
|
181
|
+
if (signatureValue.startsWith("sig1=:")) {
|
|
182
|
+
signatureValue = signatureValue.substring(6);
|
|
183
|
+
}
|
|
184
|
+
if (signatureValue.endsWith(":")) {
|
|
185
|
+
signatureValue = signatureValue.slice(0, -1);
|
|
186
|
+
}
|
|
187
|
+
const isValid = await verifyEd25519Signature(
|
|
188
|
+
key.publicKey,
|
|
189
|
+
signatureValue,
|
|
190
|
+
signatureBase
|
|
191
|
+
);
|
|
192
|
+
if (isValid) {
|
|
193
|
+
return {
|
|
194
|
+
isValid: true,
|
|
195
|
+
agent,
|
|
196
|
+
keyid: parsed.keyid,
|
|
197
|
+
confidence: 1,
|
|
198
|
+
// 100% confidence for valid signature
|
|
199
|
+
verificationMethod: "signature"
|
|
200
|
+
};
|
|
201
|
+
} else {
|
|
202
|
+
return {
|
|
203
|
+
isValid: false,
|
|
204
|
+
confidence: 0,
|
|
205
|
+
reason: "Signature verification failed",
|
|
206
|
+
verificationMethod: "none"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function hasSignatureHeaders(headers) {
|
|
211
|
+
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
212
|
+
}
|
|
213
|
+
function isChatGPTSignature(headers) {
|
|
214
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
215
|
+
return signatureAgent === '"https://chatgpt.com"' || (signatureAgent?.includes("chatgpt.com") || false);
|
|
216
|
+
}
|
|
217
|
+
|
|
5
218
|
// src/edge-detector-wrapper.ts
|
|
6
219
|
var AI_AGENT_PATTERNS = [
|
|
7
220
|
{ pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
|
|
@@ -30,16 +243,42 @@ var EdgeAgentDetector = class {
|
|
|
30
243
|
for (const [key, value] of Object.entries(headers)) {
|
|
31
244
|
normalizedHeaders[key.toLowerCase()] = value;
|
|
32
245
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
246
|
+
if (hasSignatureHeaders(headers)) {
|
|
247
|
+
try {
|
|
248
|
+
const signatureResult = await verifyAgentSignature(
|
|
249
|
+
input.method || "GET",
|
|
250
|
+
input.url || "/",
|
|
251
|
+
headers
|
|
252
|
+
);
|
|
253
|
+
if (signatureResult.isValid) {
|
|
254
|
+
confidence = signatureResult.confidence;
|
|
255
|
+
reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
|
|
256
|
+
if (signatureResult.agent) {
|
|
257
|
+
detectedAgent = {
|
|
258
|
+
type: signatureResult.agent.toLowerCase(),
|
|
259
|
+
name: signatureResult.agent
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
verificationMethod = signatureResult.verificationMethod;
|
|
263
|
+
if (signatureResult.keyid) {
|
|
264
|
+
reasons.push(`keyid:${signatureResult.keyid}`);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
confidence = Math.max(confidence, 0.3);
|
|
268
|
+
reasons.push("invalid_signature");
|
|
269
|
+
if (signatureResult.reason) {
|
|
270
|
+
reasons.push(`signature_error:${signatureResult.reason}`);
|
|
271
|
+
}
|
|
272
|
+
if (isChatGPTSignature(headers)) {
|
|
273
|
+
reasons.push("claims_chatgpt");
|
|
274
|
+
detectedAgent = { type: "chatgpt", name: "ChatGPT (unverified)" };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error("[EdgeAgentDetector] Signature verification error:", error);
|
|
279
|
+
confidence = Math.max(confidence, 0.2);
|
|
280
|
+
reasons.push("signature_verification_error");
|
|
281
|
+
}
|
|
43
282
|
}
|
|
44
283
|
const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
|
|
45
284
|
if (userAgent) {
|
|
@@ -86,7 +325,7 @@ var EdgeAgentDetector = class {
|
|
|
86
325
|
}
|
|
87
326
|
}
|
|
88
327
|
}
|
|
89
|
-
if (reasons.length > 2) {
|
|
328
|
+
if (reasons.length > 2 && confidence < 1) {
|
|
90
329
|
confidence = Math.min(confidence * 1.2, 0.95);
|
|
91
330
|
}
|
|
92
331
|
return {
|
|
@@ -95,7 +334,7 @@ var EdgeAgentDetector = class {
|
|
|
95
334
|
...detectedAgent && { detectedAgent },
|
|
96
335
|
reasons,
|
|
97
336
|
...verificationMethod && { verificationMethod },
|
|
98
|
-
forgeabilityRisk: confidence > 0.8 ? "medium" : "high",
|
|
337
|
+
forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 0.8 ? "medium" : "high",
|
|
99
338
|
timestamp: /* @__PURE__ */ new Date()
|
|
100
339
|
};
|
|
101
340
|
}
|
|
@@ -142,6 +381,34 @@ async function initWasm() {
|
|
|
142
381
|
}
|
|
143
382
|
initPromise = (async () => {
|
|
144
383
|
try {
|
|
384
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
385
|
+
try {
|
|
386
|
+
const response2 = await fetch(WASM_PATH);
|
|
387
|
+
if (!response2.ok) {
|
|
388
|
+
throw new Error(`Failed to fetch WASM: ${response2.status}`);
|
|
389
|
+
}
|
|
390
|
+
const streamResponse = response2.clone();
|
|
391
|
+
const { instance } = await WebAssembly.instantiateStreaming(
|
|
392
|
+
streamResponse,
|
|
393
|
+
{
|
|
394
|
+
wbg: {
|
|
395
|
+
__wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
|
|
396
|
+
console.log("WASM:", ptr, len);
|
|
397
|
+
},
|
|
398
|
+
__wbindgen_throw: (ptr, len) => {
|
|
399
|
+
throw new Error(`WASM Error at ${ptr}, length ${len}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
wasmInstance = instance;
|
|
405
|
+
wasmExports = instance.exports;
|
|
406
|
+
console.log("[AgentShield] \u2705 WASM module initialized with streaming");
|
|
407
|
+
return;
|
|
408
|
+
} catch (streamError) {
|
|
409
|
+
console.log("[AgentShield] Streaming compilation failed, falling back to standard compilation");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
145
412
|
const response = await fetch(WASM_PATH);
|
|
146
413
|
if (!response.ok) {
|
|
147
414
|
throw new Error(`Failed to fetch WASM: ${response.status}`);
|
|
@@ -626,11 +893,14 @@ function createAgentShieldMiddleware(config = {}) {
|
|
|
626
893
|
}
|
|
627
894
|
const userAgent = request.headers.get("user-agent");
|
|
628
895
|
const ipAddress = request.ip ?? request.headers.get("x-forwarded-for");
|
|
896
|
+
const url = new URL(request.url);
|
|
897
|
+
const pathWithQuery = url.pathname + url.search;
|
|
629
898
|
const context = {
|
|
630
899
|
...userAgent && { userAgent },
|
|
631
900
|
...ipAddress && { ipAddress },
|
|
632
901
|
headers: Object.fromEntries(request.headers.entries()),
|
|
633
|
-
url:
|
|
902
|
+
url: pathWithQuery,
|
|
903
|
+
// Use path instead of full URL for signature verification
|
|
634
904
|
method: request.method,
|
|
635
905
|
timestamp: /* @__PURE__ */ new Date()
|
|
636
906
|
};
|