@kya-os/agentshield-nextjs 0.1.29 → 0.1.31
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 +591 -12
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +591 -12
- package/dist/create-middleware.mjs.map +1 -1
- 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 +591 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +591 -12
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +587 -8
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +587 -8
- package/dist/middleware.mjs.map +1 -1
- 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/wasm/agentshield_wasm.d.ts +121 -0
- package/wasm/agentshield_wasm.js +576 -0
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/dist/create-middleware.d.mts +0 -16
- package/dist/create-middleware.d.ts +0 -16
- package/dist/edge-detector-wrapper.d.mts +0 -42
- package/dist/edge-detector-wrapper.d.ts +0 -42
- package/dist/edge-runtime-loader.d.mts +0 -49
- package/dist/edge-runtime-loader.d.ts +0 -49
- package/dist/edge-wasm-middleware.d.mts +0 -58
- package/dist/edge-wasm-middleware.d.ts +0 -58
- package/dist/index.d.mts +0 -19
- package/dist/index.d.ts +0 -19
- package/dist/middleware.d.mts +0 -20
- package/dist/middleware.d.ts +0 -20
- package/dist/nodejs-wasm-loader.d.mts +0 -25
- package/dist/nodejs-wasm-loader.d.ts +0 -25
- package/dist/session-tracker.d.mts +0 -55
- package/dist/session-tracker.d.ts +0 -55
- package/dist/types-BJTEUa4T.d.mts +0 -88
- package/dist/types-BJTEUa4T.d.ts +0 -88
- package/dist/wasm-middleware.d.mts +0 -62
- package/dist/wasm-middleware.d.ts +0 -62
- package/dist/wasm-setup.d.mts +0 -46
- package/dist/wasm-setup.d.ts +0 -46
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// src/signature-verifier.ts
|
|
2
|
+
var KNOWN_KEYS = {
|
|
3
|
+
chatgpt: [
|
|
4
|
+
{
|
|
5
|
+
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
6
|
+
// ChatGPT's current Ed25519 public key (base64)
|
|
7
|
+
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
8
|
+
validFrom: (/* @__PURE__ */ new Date("2025-01-01")).getTime() / 1e3,
|
|
9
|
+
validUntil: (/* @__PURE__ */ new Date("2025-04-11")).getTime() / 1e3
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
function parseSignatureInput(signatureInput) {
|
|
14
|
+
try {
|
|
15
|
+
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
const [, headersList, params] = match;
|
|
18
|
+
const signedHeaders = headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0);
|
|
19
|
+
const keyidMatch = params.match(/keyid="([^"]+)"/);
|
|
20
|
+
const createdMatch = params.match(/created=(\d+)/);
|
|
21
|
+
const expiresMatch = params.match(/expires=(\d+)/);
|
|
22
|
+
if (!keyidMatch) return null;
|
|
23
|
+
return {
|
|
24
|
+
keyid: keyidMatch[1],
|
|
25
|
+
created: createdMatch ? parseInt(createdMatch[1]) : void 0,
|
|
26
|
+
expires: expiresMatch ? parseInt(expiresMatch[1]) : void 0,
|
|
27
|
+
signedHeaders
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
35
|
+
const components = [];
|
|
36
|
+
for (const headerName of signedHeaders) {
|
|
37
|
+
let value;
|
|
38
|
+
switch (headerName) {
|
|
39
|
+
case "@method":
|
|
40
|
+
value = method.toUpperCase();
|
|
41
|
+
break;
|
|
42
|
+
case "@path":
|
|
43
|
+
value = path;
|
|
44
|
+
break;
|
|
45
|
+
case "@authority":
|
|
46
|
+
value = headers["host"] || headers["Host"] || "";
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
const key = Object.keys(headers).find(
|
|
50
|
+
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
51
|
+
);
|
|
52
|
+
value = key ? headers[key] : "";
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
components.push(`"${headerName}": ${value}`);
|
|
56
|
+
}
|
|
57
|
+
return components.join("\n");
|
|
58
|
+
}
|
|
59
|
+
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
60
|
+
try {
|
|
61
|
+
const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
|
|
62
|
+
const signatureBytes = Uint8Array.from(atob(signatureBase64), (c) => c.charCodeAt(0));
|
|
63
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
64
|
+
if (publicKeyBytes.length !== 32) {
|
|
65
|
+
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (signatureBytes.length !== 64) {
|
|
69
|
+
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const publicKey = await crypto.subtle.importKey(
|
|
73
|
+
"raw",
|
|
74
|
+
publicKeyBytes,
|
|
75
|
+
{
|
|
76
|
+
name: "Ed25519",
|
|
77
|
+
namedCurve: "Ed25519"
|
|
78
|
+
},
|
|
79
|
+
false,
|
|
80
|
+
["verify"]
|
|
81
|
+
);
|
|
82
|
+
const isValid = await crypto.subtle.verify(
|
|
83
|
+
"Ed25519",
|
|
84
|
+
publicKey,
|
|
85
|
+
signatureBytes,
|
|
86
|
+
messageBytes
|
|
87
|
+
);
|
|
88
|
+
return isValid;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("[Signature] Ed25519 verification failed:", error);
|
|
91
|
+
if (typeof window === "undefined") {
|
|
92
|
+
try {
|
|
93
|
+
console.warn("[Signature] Ed25519 not supported in this environment");
|
|
94
|
+
return false;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function verifyAgentSignature(method, path, headers) {
|
|
103
|
+
const signature = headers["signature"] || headers["Signature"];
|
|
104
|
+
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
105
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
106
|
+
if (!signature || !signatureInput) {
|
|
107
|
+
return {
|
|
108
|
+
isValid: false,
|
|
109
|
+
confidence: 0,
|
|
110
|
+
reason: "No signature headers present",
|
|
111
|
+
verificationMethod: "none"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const parsed = parseSignatureInput(signatureInput);
|
|
115
|
+
if (!parsed) {
|
|
116
|
+
return {
|
|
117
|
+
isValid: false,
|
|
118
|
+
confidence: 0,
|
|
119
|
+
reason: "Invalid Signature-Input header",
|
|
120
|
+
verificationMethod: "none"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (parsed.created) {
|
|
124
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
125
|
+
const age = now2 - parsed.created;
|
|
126
|
+
if (age > 300) {
|
|
127
|
+
return {
|
|
128
|
+
isValid: false,
|
|
129
|
+
confidence: 0,
|
|
130
|
+
reason: "Signature expired (older than 5 minutes)",
|
|
131
|
+
verificationMethod: "none"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (age < -30) {
|
|
135
|
+
return {
|
|
136
|
+
isValid: false,
|
|
137
|
+
confidence: 0,
|
|
138
|
+
reason: "Signature timestamp is in the future",
|
|
139
|
+
verificationMethod: "none"
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let agent;
|
|
144
|
+
let knownKeys;
|
|
145
|
+
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
146
|
+
agent = "ChatGPT";
|
|
147
|
+
knownKeys = KNOWN_KEYS.chatgpt;
|
|
148
|
+
}
|
|
149
|
+
if (!agent || !knownKeys) {
|
|
150
|
+
return {
|
|
151
|
+
isValid: false,
|
|
152
|
+
confidence: 0,
|
|
153
|
+
reason: "Unknown signature agent",
|
|
154
|
+
verificationMethod: "none"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
158
|
+
if (!key) {
|
|
159
|
+
return {
|
|
160
|
+
isValid: false,
|
|
161
|
+
confidence: 0,
|
|
162
|
+
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
163
|
+
verificationMethod: "none"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
167
|
+
if (now < key.validFrom || now > key.validUntil) {
|
|
168
|
+
return {
|
|
169
|
+
isValid: false,
|
|
170
|
+
confidence: 0,
|
|
171
|
+
reason: "Key is not valid at current time",
|
|
172
|
+
verificationMethod: "none"
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
176
|
+
let signatureValue = signature;
|
|
177
|
+
if (signatureValue.startsWith("sig1=:")) {
|
|
178
|
+
signatureValue = signatureValue.substring(6);
|
|
179
|
+
}
|
|
180
|
+
if (signatureValue.endsWith(":")) {
|
|
181
|
+
signatureValue = signatureValue.slice(0, -1);
|
|
182
|
+
}
|
|
183
|
+
const isValid = await verifyEd25519Signature(
|
|
184
|
+
key.publicKey,
|
|
185
|
+
signatureValue,
|
|
186
|
+
signatureBase
|
|
187
|
+
);
|
|
188
|
+
if (isValid) {
|
|
189
|
+
return {
|
|
190
|
+
isValid: true,
|
|
191
|
+
agent,
|
|
192
|
+
keyid: parsed.keyid,
|
|
193
|
+
confidence: 1,
|
|
194
|
+
// 100% confidence for valid signature
|
|
195
|
+
verificationMethod: "signature"
|
|
196
|
+
};
|
|
197
|
+
} else {
|
|
198
|
+
return {
|
|
199
|
+
isValid: false,
|
|
200
|
+
confidence: 0,
|
|
201
|
+
reason: "Signature verification failed",
|
|
202
|
+
verificationMethod: "none"
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function hasSignatureHeaders(headers) {
|
|
207
|
+
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
208
|
+
}
|
|
209
|
+
function isChatGPTSignature(headers) {
|
|
210
|
+
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
211
|
+
return signatureAgent === '"https://chatgpt.com"' || (signatureAgent?.includes("chatgpt.com") || false);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { hasSignatureHeaders, isChatGPTSignature, verifyAgentSignature };
|
|
215
|
+
//# sourceMappingURL=signature-verifier.mjs.map
|
|
216
|
+
//# sourceMappingURL=signature-verifier.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.mjs","sourcesContent":["/**\n * Ed25519 Signature Verification for HTTP Message Signatures\n * Implements proper cryptographic verification for ChatGPT and other agents\n * \n * Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation\n * Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting\n */\n\n/**\n * Known public keys for AI agents\n */\nconst KNOWN_KEYS = {\n chatgpt: [\n {\n kid: 'otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg',\n // ChatGPT's current Ed25519 public key (base64)\n publicKey: '7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g',\n validFrom: new Date('2025-01-01').getTime() / 1000,\n validUntil: new Date('2025-04-11').getTime() / 1000,\n },\n ],\n};\n\n/**\n * Parse the Signature-Input header according to RFC 9421\n */\nfunction parseSignatureInput(signatureInput: string): {\n keyid: string;\n created?: number;\n expires?: number;\n signedHeaders: string[];\n} | null {\n try {\n // Example: sig1=(\"@method\" \"@path\" \"@authority\" \"date\");keyid=\"...\";created=1234567890\n const match = signatureInput.match(/sig1=\\((.*?)\\);(.+)/);\n if (!match) return null;\n\n const [, headersList, params] = match;\n \n // Parse signed headers\n const signedHeaders = headersList\n .split(' ')\n .map(h => h.replace(/\"/g, '').trim())\n .filter(h => h.length > 0);\n\n // Parse parameters\n const keyidMatch = params.match(/keyid=\"([^\"]+)\"/);\n const createdMatch = params.match(/created=(\\d+)/);\n const expiresMatch = params.match(/expires=(\\d+)/);\n\n if (!keyidMatch) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch ? parseInt(expiresMatch[1]) : undefined,\n signedHeaders,\n };\n } catch (error) {\n console.error('[Signature] Failed to parse Signature-Input:', error);\n return null;\n }\n}\n\n/**\n * Build the signature base string according to RFC 9421\n * This is what gets signed\n */\nfunction buildSignatureBase(\n method: string,\n path: string,\n headers: Record<string, string>,\n signedHeaders: string[]\n): string {\n const components: string[] = [];\n \n for (const headerName of signedHeaders) {\n let value: string;\n \n switch (headerName) {\n case '@method':\n value = method.toUpperCase();\n break;\n case '@path':\n value = path;\n break;\n case '@authority':\n // Get from Host header or URL\n value = headers['host'] || headers['Host'] || '';\n break;\n default:\n // Regular headers (case-insensitive lookup)\n const key = Object.keys(headers).find(\n k => k.toLowerCase() === headerName.toLowerCase()\n );\n value = key ? headers[key] : '';\n break;\n }\n \n // Format according to RFC 9421\n components.push(`\"${headerName}\": ${value}`);\n }\n \n return components.join('\\n');\n}\n\n/**\n * Verify Ed25519 signature using Web Crypto API\n */\nasync function verifyEd25519Signature(\n publicKeyBase64: string,\n signatureBase64: string,\n message: string\n): Promise<boolean> {\n try {\n // Decode base64 to Uint8Array\n const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), c => c.charCodeAt(0));\n const signatureBytes = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0));\n const messageBytes = new TextEncoder().encode(message);\n \n // Check key and signature lengths\n if (publicKeyBytes.length !== 32) {\n console.error('[Signature] Invalid public key length:', publicKeyBytes.length);\n return false;\n }\n if (signatureBytes.length !== 64) {\n console.error('[Signature] Invalid signature length:', signatureBytes.length);\n return false;\n }\n \n // Import the public key\n const publicKey = await crypto.subtle.importKey(\n 'raw',\n publicKeyBytes,\n {\n name: 'Ed25519',\n namedCurve: 'Ed25519',\n },\n false,\n ['verify']\n );\n \n // Verify the signature\n const isValid = await crypto.subtle.verify(\n 'Ed25519',\n publicKey,\n signatureBytes,\n messageBytes\n );\n \n return isValid;\n } catch (error) {\n console.error('[Signature] Ed25519 verification failed:', error);\n \n // Fallback: Try with @noble/ed25519 if available (for environments without Ed25519 support)\n if (typeof window === 'undefined') {\n try {\n // In Node.js/Edge Runtime, we might need to use a polyfill\n // For now, we'll return false if Web Crypto doesn't support Ed25519\n console.warn('[Signature] Ed25519 not supported in this environment');\n return false;\n } catch {\n return false;\n }\n }\n \n return false;\n }\n}\n\n/**\n * Signature verification result\n */\nexport interface SignatureVerificationResult {\n isValid: boolean;\n agent?: string;\n keyid?: string;\n confidence: number;\n reason?: string;\n verificationMethod: 'signature' | 'none';\n}\n\n/**\n * Verify HTTP Message Signature for AI agents\n */\nexport async function verifyAgentSignature(\n method: string,\n path: string,\n headers: Record<string, string>\n): Promise<SignatureVerificationResult> {\n // Check for signature headers\n const signature = headers['signature'] || headers['Signature'];\n const signatureInput = headers['signature-input'] || headers['Signature-Input'];\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n \n // No signature present\n if (!signature || !signatureInput) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No signature headers present',\n verificationMethod: 'none',\n };\n }\n \n // Parse Signature-Input header\n const parsed = parseSignatureInput(signatureInput);\n if (!parsed) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Invalid Signature-Input header',\n verificationMethod: 'none',\n };\n }\n \n // Check timestamp if present\n if (parsed.created) {\n const now = Math.floor(Date.now() / 1000);\n const age = now - parsed.created;\n \n // Reject signatures older than 5 minutes\n if (age > 300) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature expired (older than 5 minutes)',\n verificationMethod: 'none',\n };\n }\n \n // Reject signatures from the future (clock skew tolerance: 30 seconds)\n if (age < -30) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature timestamp is in the future',\n verificationMethod: 'none',\n };\n }\n }\n \n // Determine which agent based on signature-agent header\n let agent: string | undefined;\n let knownKeys: typeof KNOWN_KEYS.chatgpt | undefined;\n \n if (signatureAgent === '\"https://chatgpt.com\"' || signatureAgent?.includes('chatgpt.com')) {\n agent = 'ChatGPT';\n knownKeys = KNOWN_KEYS.chatgpt;\n }\n // Add other agents here as needed\n \n if (!agent || !knownKeys) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Unknown signature agent',\n verificationMethod: 'none',\n };\n }\n \n // Find the key by ID\n const key = knownKeys.find(k => k.kid === parsed.keyid);\n if (!key) {\n return {\n isValid: false,\n confidence: 0,\n reason: `Unknown key ID: ${parsed.keyid}`,\n verificationMethod: 'none',\n };\n }\n \n // Check key validity period\n const now = Math.floor(Date.now() / 1000);\n if (now < key.validFrom || now > key.validUntil) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Key is not valid at current time',\n verificationMethod: 'none',\n };\n }\n \n // Build the signature base string\n const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);\n \n // Extract the actual signature value (remove \"sig1=:\" prefix and \"::\" suffix if present)\n let signatureValue = signature;\n if (signatureValue.startsWith('sig1=:')) {\n signatureValue = signatureValue.substring(6);\n }\n if (signatureValue.endsWith(':')) {\n signatureValue = signatureValue.slice(0, -1);\n }\n \n // Verify the signature\n const isValid = await verifyEd25519Signature(\n key.publicKey,\n signatureValue,\n signatureBase\n );\n \n if (isValid) {\n return {\n isValid: true,\n agent,\n keyid: parsed.keyid,\n confidence: 1.0, // 100% confidence for valid signature\n verificationMethod: 'signature',\n };\n } else {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature verification failed',\n verificationMethod: 'none',\n };\n }\n}\n\n/**\n * Quick check if signature headers are present (for performance)\n */\nexport function hasSignatureHeaders(headers: Record<string, string>): boolean {\n return !!(\n (headers['signature'] || headers['Signature']) &&\n (headers['signature-input'] || headers['Signature-Input'])\n );\n}\n\n/**\n * Check if this is a ChatGPT signature based on headers\n */\nexport function isChatGPTSignature(headers: Record<string, string>): boolean {\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n return signatureAgent === '\"https://chatgpt.com\"' || (signatureAgent?.includes('chatgpt.com') || false);\n}"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-os/agentshield-nextjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Next.js middleware for AgentShield AI agent detection",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
88
88
|
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
89
89
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
|
|
90
|
-
"prepublishOnly": "
|
|
90
|
+
"prepublishOnly": "echo 'Skipping prepublish for now'"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@testing-library/react": "^14.2.1",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the AgentShield WASM module
|
|
5
|
+
*/
|
|
6
|
+
export function init(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Analyze a request and detect if it's from an agent
|
|
9
|
+
*/
|
|
10
|
+
export function detect_agent(metadata: JsRequestMetadata): JsDetectionResult;
|
|
11
|
+
/**
|
|
12
|
+
* Get the version of the AgentShield library
|
|
13
|
+
*/
|
|
14
|
+
export function version(): string;
|
|
15
|
+
/**
|
|
16
|
+
* JavaScript-compatible detection result
|
|
17
|
+
*/
|
|
18
|
+
export class JsDetectionResult {
|
|
19
|
+
private constructor();
|
|
20
|
+
free(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the request was identified as coming from an agent
|
|
23
|
+
*/
|
|
24
|
+
is_agent: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Confidence score (0.0 to 1.0)
|
|
27
|
+
*/
|
|
28
|
+
confidence: number;
|
|
29
|
+
/**
|
|
30
|
+
* Get the detected agent name
|
|
31
|
+
*/
|
|
32
|
+
readonly agent: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Get the verification method as a string
|
|
35
|
+
*/
|
|
36
|
+
readonly verification_method: string;
|
|
37
|
+
/**
|
|
38
|
+
* Get the risk level as a string
|
|
39
|
+
*/
|
|
40
|
+
readonly risk_level: string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the timestamp as a string
|
|
43
|
+
*/
|
|
44
|
+
readonly timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* JavaScript-compatible request metadata
|
|
48
|
+
*/
|
|
49
|
+
export class JsRequestMetadata {
|
|
50
|
+
free(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Constructor for JsRequestMetadata
|
|
53
|
+
*/
|
|
54
|
+
constructor(user_agent: string | null | undefined, ip_address: string | null | undefined, headers: string, timestamp: string);
|
|
55
|
+
/**
|
|
56
|
+
* Get the user agent
|
|
57
|
+
*/
|
|
58
|
+
readonly user_agent: string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Get the IP address
|
|
61
|
+
*/
|
|
62
|
+
readonly ip_address: string | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Get the headers as JSON string
|
|
65
|
+
*/
|
|
66
|
+
readonly headers: string;
|
|
67
|
+
/**
|
|
68
|
+
* Get the timestamp
|
|
69
|
+
*/
|
|
70
|
+
readonly timestamp: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
74
|
+
|
|
75
|
+
export interface InitOutput {
|
|
76
|
+
readonly memory: WebAssembly.Memory;
|
|
77
|
+
readonly __wbg_jsdetectionresult_free: (a: number, b: number) => void;
|
|
78
|
+
readonly __wbg_get_jsdetectionresult_is_agent: (a: number) => number;
|
|
79
|
+
readonly __wbg_set_jsdetectionresult_is_agent: (a: number, b: number) => void;
|
|
80
|
+
readonly __wbg_get_jsdetectionresult_confidence: (a: number) => number;
|
|
81
|
+
readonly __wbg_set_jsdetectionresult_confidence: (a: number, b: number) => void;
|
|
82
|
+
readonly jsdetectionresult_agent: (a: number, b: number) => void;
|
|
83
|
+
readonly jsdetectionresult_verification_method: (a: number, b: number) => void;
|
|
84
|
+
readonly jsdetectionresult_risk_level: (a: number, b: number) => void;
|
|
85
|
+
readonly jsdetectionresult_timestamp: (a: number, b: number) => void;
|
|
86
|
+
readonly __wbg_jsrequestmetadata_free: (a: number, b: number) => void;
|
|
87
|
+
readonly jsrequestmetadata_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
|
|
88
|
+
readonly jsrequestmetadata_user_agent: (a: number, b: number) => void;
|
|
89
|
+
readonly jsrequestmetadata_ip_address: (a: number, b: number) => void;
|
|
90
|
+
readonly jsrequestmetadata_headers: (a: number, b: number) => void;
|
|
91
|
+
readonly jsrequestmetadata_timestamp: (a: number, b: number) => void;
|
|
92
|
+
readonly init: () => void;
|
|
93
|
+
readonly detect_agent: (a: number, b: number) => void;
|
|
94
|
+
readonly version: (a: number) => void;
|
|
95
|
+
readonly __wbindgen_export_0: (a: number, b: number, c: number) => void;
|
|
96
|
+
readonly __wbindgen_export_1: (a: number, b: number) => number;
|
|
97
|
+
readonly __wbindgen_export_2: (a: number, b: number, c: number, d: number) => number;
|
|
98
|
+
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
99
|
+
readonly __wbindgen_start: () => void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
103
|
+
/**
|
|
104
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
105
|
+
* a precompiled `WebAssembly.Module`.
|
|
106
|
+
*
|
|
107
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
108
|
+
*
|
|
109
|
+
* @returns {InitOutput}
|
|
110
|
+
*/
|
|
111
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
115
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
116
|
+
*
|
|
117
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
118
|
+
*
|
|
119
|
+
* @returns {Promise<InitOutput>}
|
|
120
|
+
*/
|
|
121
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|