@kya-os/agentshield-nextjs 0.1.31 → 0.1.33
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.d.mts +16 -0
- package/dist/create-middleware.d.ts +16 -0
- package/dist/create-middleware.js +8 -8
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +8 -8
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge-detector-wrapper.d.mts +43 -0
- package/dist/edge-detector-wrapper.d.ts +43 -0
- package/dist/edge-detector-wrapper.js +8 -8
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +8 -8
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/edge-runtime-loader.d.mts +49 -0
- package/dist/edge-runtime-loader.d.ts +49 -0
- package/dist/edge-wasm-middleware.d.mts +58 -0
- package/dist/edge-wasm-middleware.d.ts +58 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +20 -0
- package/dist/middleware.d.ts +20 -0
- package/dist/middleware.js +8 -8
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +8 -8
- package/dist/middleware.mjs.map +1 -1
- package/dist/nodejs-wasm-loader.d.mts +25 -0
- package/dist/nodejs-wasm-loader.d.ts +25 -0
- package/dist/session-tracker.d.mts +55 -0
- package/dist/session-tracker.d.ts +55 -0
- package/dist/signature-verifier.d.mts +32 -0
- package/dist/signature-verifier.d.ts +32 -0
- package/dist/signature-verifier.js +8 -8
- package/dist/signature-verifier.js.map +1 -1
- package/dist/signature-verifier.mjs +8 -8
- package/dist/signature-verifier.mjs.map +1 -1
- package/dist/types-BJTEUa4T.d.mts +88 -0
- package/dist/types-BJTEUa4T.d.ts +88 -0
- package/dist/wasm-middleware.d.mts +62 -0
- package/dist/wasm-middleware.d.ts +62 -0
- package/dist/wasm-setup.d.mts +46 -0
- package/dist/wasm-setup.d.ts +46 -0
- package/package.json +3 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ed25519 Signature Verification for HTTP Message Signatures
|
|
3
|
+
* Implements proper cryptographic verification for ChatGPT and other agents
|
|
4
|
+
*
|
|
5
|
+
* Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation
|
|
6
|
+
* Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Signature verification result
|
|
10
|
+
*/
|
|
11
|
+
interface SignatureVerificationResult {
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
agent?: string;
|
|
14
|
+
keyid?: string;
|
|
15
|
+
confidence: number;
|
|
16
|
+
reason?: string;
|
|
17
|
+
verificationMethod: 'signature' | 'none';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify HTTP Message Signature for AI agents
|
|
21
|
+
*/
|
|
22
|
+
declare function verifyAgentSignature(method: string, path: string, headers: Record<string, string>): Promise<SignatureVerificationResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Quick check if signature headers are present (for performance)
|
|
25
|
+
*/
|
|
26
|
+
declare function hasSignatureHeaders(headers: Record<string, string>): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Check if this is a ChatGPT signature based on headers
|
|
29
|
+
*/
|
|
30
|
+
declare function isChatGPTSignature(headers: Record<string, string>): boolean;
|
|
31
|
+
|
|
32
|
+
export { type SignatureVerificationResult, hasSignatureHeaders, isChatGPTSignature, verifyAgentSignature };
|
|
@@ -17,15 +17,15 @@ function parseSignatureInput(signatureInput) {
|
|
|
17
17
|
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
18
18
|
if (!match) return null;
|
|
19
19
|
const [, headersList, params] = match;
|
|
20
|
-
const signedHeaders = headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0);
|
|
21
|
-
const keyidMatch = params.match(/keyid="([^"]+)"/);
|
|
22
|
-
const createdMatch = params.match(/created=(\d+)/);
|
|
23
|
-
const expiresMatch = params.match(/expires=(\d+)/);
|
|
24
|
-
if (!keyidMatch) return null;
|
|
20
|
+
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
21
|
+
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
22
|
+
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
23
|
+
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
24
|
+
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
25
25
|
return {
|
|
26
26
|
keyid: keyidMatch[1],
|
|
27
|
-
created: createdMatch ? parseInt(createdMatch[1]) : void 0,
|
|
28
|
-
expires: expiresMatch ? parseInt(expiresMatch[1]) : void 0,
|
|
27
|
+
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
28
|
+
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
29
29
|
signedHeaders
|
|
30
30
|
};
|
|
31
31
|
} catch (error) {
|
|
@@ -51,7 +51,7 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
|
51
51
|
const key = Object.keys(headers).find(
|
|
52
52
|
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
53
53
|
);
|
|
54
|
-
value = key ? headers[key] : "";
|
|
54
|
+
value = key ? headers[key] || "" : "";
|
|
55
55
|
break;
|
|
56
56
|
}
|
|
57
57
|
components.push(`"${headerName}": ${value}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.js","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}"]}
|
|
1
|
+
{"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.js","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 | undefined;\n expires?: number | undefined;\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 ? headersList\n .split(' ')\n .map(h => h.replace(/\"/g, '').trim())\n .filter(h => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? 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}"]}
|
|
@@ -15,15 +15,15 @@ function parseSignatureInput(signatureInput) {
|
|
|
15
15
|
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
16
16
|
if (!match) return null;
|
|
17
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;
|
|
18
|
+
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
19
|
+
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
20
|
+
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
21
|
+
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
22
|
+
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
23
23
|
return {
|
|
24
24
|
keyid: keyidMatch[1],
|
|
25
|
-
created: createdMatch ? parseInt(createdMatch[1]) : void 0,
|
|
26
|
-
expires: expiresMatch ? parseInt(expiresMatch[1]) : void 0,
|
|
25
|
+
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
26
|
+
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
27
27
|
signedHeaders
|
|
28
28
|
};
|
|
29
29
|
} catch (error) {
|
|
@@ -49,7 +49,7 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
|
49
49
|
const key = Object.keys(headers).find(
|
|
50
50
|
(k) => k.toLowerCase() === headerName.toLowerCase()
|
|
51
51
|
);
|
|
52
|
-
value = key ? headers[key] : "";
|
|
52
|
+
value = key ? headers[key] || "" : "";
|
|
53
53
|
break;
|
|
54
54
|
}
|
|
55
55
|
components.push(`"${headerName}": ${value}`);
|
|
@@ -1 +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}"]}
|
|
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 | undefined;\n expires?: number | undefined;\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 ? headersList\n .split(' ')\n .map(h => h.replace(/\"/g, '').trim())\n .filter(h => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? 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}"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { AgentShieldConfig, DetectionResult, AgentShieldEvents } from '@kya-os/agentshield';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Next.js-specific type definitions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Next.js middleware configuration
|
|
10
|
+
*/
|
|
11
|
+
interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
|
|
12
|
+
/**
|
|
13
|
+
* Action to take when an agent is detected
|
|
14
|
+
*/
|
|
15
|
+
onAgentDetected?: 'block' | 'redirect' | 'rewrite' | 'allow' | 'log';
|
|
16
|
+
/**
|
|
17
|
+
* Custom handler for agent detection
|
|
18
|
+
* @deprecated Use 'events' instead. Will be removed in v1.0.0
|
|
19
|
+
*/
|
|
20
|
+
onDetection?: (req: NextRequest, result: DetectionResult) => NextResponse | Promise<NextResponse> | void | Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Event handlers for detection events
|
|
23
|
+
*/
|
|
24
|
+
events?: Partial<AgentShieldEvents>;
|
|
25
|
+
/**
|
|
26
|
+
* Path patterns to skip detection
|
|
27
|
+
*/
|
|
28
|
+
skipPaths?: string[] | RegExp[];
|
|
29
|
+
/**
|
|
30
|
+
* Response when blocking agents
|
|
31
|
+
*/
|
|
32
|
+
blockedResponse?: {
|
|
33
|
+
status: number;
|
|
34
|
+
message: string;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Redirect URL when redirecting detected agents
|
|
39
|
+
*/
|
|
40
|
+
redirectUrl?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Rewrite URL when rewriting requests from detected agents
|
|
43
|
+
*/
|
|
44
|
+
rewriteUrl?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Confidence threshold for agent detection
|
|
47
|
+
*/
|
|
48
|
+
confidenceThreshold?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Enable WASM for enhanced detection
|
|
51
|
+
*/
|
|
52
|
+
enableWasm?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Session tracking configuration
|
|
55
|
+
*/
|
|
56
|
+
sessionTracking?: {
|
|
57
|
+
/**
|
|
58
|
+
* Enable session tracking
|
|
59
|
+
*/
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Cookie name for session storage
|
|
63
|
+
* Default: '__agentshield_session'
|
|
64
|
+
*/
|
|
65
|
+
cookieName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Cookie max age in seconds
|
|
68
|
+
* Default: 3600 (1 hour)
|
|
69
|
+
*/
|
|
70
|
+
cookieMaxAge?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Encryption key for session data
|
|
73
|
+
* Default: Uses AGENTSHIELD_SECRET env var or default key
|
|
74
|
+
*/
|
|
75
|
+
encryptionKey?: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Detection context for hooks
|
|
80
|
+
*/
|
|
81
|
+
interface DetectionContext {
|
|
82
|
+
result: DetectionResult;
|
|
83
|
+
request: NextRequest;
|
|
84
|
+
userAgent?: string;
|
|
85
|
+
ip?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type { DetectionContext as D, NextJSMiddlewareConfig as N };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { AgentShieldConfig, DetectionResult, AgentShieldEvents } from '@kya-os/agentshield';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Next.js-specific type definitions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Next.js middleware configuration
|
|
10
|
+
*/
|
|
11
|
+
interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
|
|
12
|
+
/**
|
|
13
|
+
* Action to take when an agent is detected
|
|
14
|
+
*/
|
|
15
|
+
onAgentDetected?: 'block' | 'redirect' | 'rewrite' | 'allow' | 'log';
|
|
16
|
+
/**
|
|
17
|
+
* Custom handler for agent detection
|
|
18
|
+
* @deprecated Use 'events' instead. Will be removed in v1.0.0
|
|
19
|
+
*/
|
|
20
|
+
onDetection?: (req: NextRequest, result: DetectionResult) => NextResponse | Promise<NextResponse> | void | Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Event handlers for detection events
|
|
23
|
+
*/
|
|
24
|
+
events?: Partial<AgentShieldEvents>;
|
|
25
|
+
/**
|
|
26
|
+
* Path patterns to skip detection
|
|
27
|
+
*/
|
|
28
|
+
skipPaths?: string[] | RegExp[];
|
|
29
|
+
/**
|
|
30
|
+
* Response when blocking agents
|
|
31
|
+
*/
|
|
32
|
+
blockedResponse?: {
|
|
33
|
+
status: number;
|
|
34
|
+
message: string;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Redirect URL when redirecting detected agents
|
|
39
|
+
*/
|
|
40
|
+
redirectUrl?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Rewrite URL when rewriting requests from detected agents
|
|
43
|
+
*/
|
|
44
|
+
rewriteUrl?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Confidence threshold for agent detection
|
|
47
|
+
*/
|
|
48
|
+
confidenceThreshold?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Enable WASM for enhanced detection
|
|
51
|
+
*/
|
|
52
|
+
enableWasm?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Session tracking configuration
|
|
55
|
+
*/
|
|
56
|
+
sessionTracking?: {
|
|
57
|
+
/**
|
|
58
|
+
* Enable session tracking
|
|
59
|
+
*/
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Cookie name for session storage
|
|
63
|
+
* Default: '__agentshield_session'
|
|
64
|
+
*/
|
|
65
|
+
cookieName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Cookie max age in seconds
|
|
68
|
+
* Default: 3600 (1 hour)
|
|
69
|
+
*/
|
|
70
|
+
cookieMaxAge?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Encryption key for session data
|
|
73
|
+
* Default: Uses AGENTSHIELD_SECRET env var or default key
|
|
74
|
+
*/
|
|
75
|
+
encryptionKey?: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Detection context for hooks
|
|
80
|
+
*/
|
|
81
|
+
interface DetectionContext {
|
|
82
|
+
result: DetectionResult;
|
|
83
|
+
request: NextRequest;
|
|
84
|
+
userAgent?: string;
|
|
85
|
+
ip?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type { DetectionContext as D, NextJSMiddlewareConfig as N };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WASM-enabled middleware for Next.js with AgentShield
|
|
5
|
+
* Following official Next.js documentation for WebAssembly in Edge Runtime
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface WasmDetectionResult {
|
|
9
|
+
isAgent: boolean;
|
|
10
|
+
confidence: number;
|
|
11
|
+
agent?: string | undefined;
|
|
12
|
+
verificationMethod: 'signature' | 'pattern' | 'none';
|
|
13
|
+
riskLevel: 'low' | 'medium' | 'high';
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
interface AgentShieldConfig {
|
|
17
|
+
onAgentDetected?: (result: WasmDetectionResult) => void | Promise<void>;
|
|
18
|
+
blockOnHighConfidence?: boolean;
|
|
19
|
+
confidenceThreshold?: number;
|
|
20
|
+
skipPaths?: string[];
|
|
21
|
+
blockedResponse?: {
|
|
22
|
+
status?: number;
|
|
23
|
+
message?: string;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a WASM-enabled AgentShield middleware
|
|
29
|
+
* This must be used with proper WASM module import at the top of middleware.ts
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // middleware.ts
|
|
34
|
+
* import wasmModule from '@kya-os/agentshield/wasm?module';
|
|
35
|
+
* import { createWasmAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';
|
|
36
|
+
*
|
|
37
|
+
* const wasmInstance = await WebAssembly.instantiate(wasmModule);
|
|
38
|
+
*
|
|
39
|
+
* export const middleware = createWasmAgentShieldMiddleware({
|
|
40
|
+
* wasmInstance,
|
|
41
|
+
* onAgentDetected: (result) => {
|
|
42
|
+
* console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function createWasmAgentShieldMiddleware(config: AgentShieldConfig & {
|
|
48
|
+
wasmInstance?: WebAssembly.Instance;
|
|
49
|
+
}): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to load and instantiate WASM module
|
|
52
|
+
* This should be called at the top of your middleware.ts file
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import wasmModule from '@kya-os/agentshield/wasm?module';
|
|
57
|
+
* const wasmInstance = await instantiateWasm(wasmModule);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function instantiateWasm(wasmModule: WebAssembly.Module): Promise<WebAssembly.Instance>;
|
|
61
|
+
|
|
62
|
+
export { type AgentShieldConfig, type WasmDetectionResult, createWasmAgentShieldMiddleware, instantiateWasm };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WASM-enabled middleware for Next.js with AgentShield
|
|
5
|
+
* Following official Next.js documentation for WebAssembly in Edge Runtime
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface WasmDetectionResult {
|
|
9
|
+
isAgent: boolean;
|
|
10
|
+
confidence: number;
|
|
11
|
+
agent?: string | undefined;
|
|
12
|
+
verificationMethod: 'signature' | 'pattern' | 'none';
|
|
13
|
+
riskLevel: 'low' | 'medium' | 'high';
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
interface AgentShieldConfig {
|
|
17
|
+
onAgentDetected?: (result: WasmDetectionResult) => void | Promise<void>;
|
|
18
|
+
blockOnHighConfidence?: boolean;
|
|
19
|
+
confidenceThreshold?: number;
|
|
20
|
+
skipPaths?: string[];
|
|
21
|
+
blockedResponse?: {
|
|
22
|
+
status?: number;
|
|
23
|
+
message?: string;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a WASM-enabled AgentShield middleware
|
|
29
|
+
* This must be used with proper WASM module import at the top of middleware.ts
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // middleware.ts
|
|
34
|
+
* import wasmModule from '@kya-os/agentshield/wasm?module';
|
|
35
|
+
* import { createWasmAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';
|
|
36
|
+
*
|
|
37
|
+
* const wasmInstance = await WebAssembly.instantiate(wasmModule);
|
|
38
|
+
*
|
|
39
|
+
* export const middleware = createWasmAgentShieldMiddleware({
|
|
40
|
+
* wasmInstance,
|
|
41
|
+
* onAgentDetected: (result) => {
|
|
42
|
+
* console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function createWasmAgentShieldMiddleware(config: AgentShieldConfig & {
|
|
48
|
+
wasmInstance?: WebAssembly.Instance;
|
|
49
|
+
}): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to load and instantiate WASM module
|
|
52
|
+
* This should be called at the top of your middleware.ts file
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import wasmModule from '@kya-os/agentshield/wasm?module';
|
|
57
|
+
* const wasmInstance = await instantiateWasm(wasmModule);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function instantiateWasm(wasmModule: WebAssembly.Module): Promise<WebAssembly.Instance>;
|
|
61
|
+
|
|
62
|
+
export { type AgentShieldConfig, type WasmDetectionResult, createWasmAgentShieldMiddleware, instantiateWasm };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM Setup for AgentShield in Next.js Edge Runtime
|
|
3
|
+
*
|
|
4
|
+
* This module handles WASM initialization for cryptographic signature verification.
|
|
5
|
+
* Designed to work without top-level await to avoid Next.js middleware issues.
|
|
6
|
+
*
|
|
7
|
+
* Usage in middleware.ts:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { setupWasm } from '@kya-os/agentshield-nextjs/wasm-setup';
|
|
10
|
+
* import { createAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';
|
|
11
|
+
*
|
|
12
|
+
* export async function middleware(request: NextRequest) {
|
|
13
|
+
* // Initialize WASM inside the middleware function
|
|
14
|
+
* await setupWasm();
|
|
15
|
+
*
|
|
16
|
+
* const agentShieldMiddleware = createAgentShieldMiddleware({...});
|
|
17
|
+
* return agentShieldMiddleware(request);
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Initialize WASM module for AgentShield
|
|
23
|
+
*
|
|
24
|
+
* This function:
|
|
25
|
+
* - Loads WASM in production/Edge Runtime for cryptographic verification
|
|
26
|
+
* - Skips WASM in test environments (Jest) automatically
|
|
27
|
+
* - Is safe to call multiple times (idempotent)
|
|
28
|
+
* - Handles errors gracefully with fallback to pattern detection
|
|
29
|
+
*
|
|
30
|
+
* @returns Promise that resolves when initialization is complete
|
|
31
|
+
*/
|
|
32
|
+
declare function setupWasm(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Check if WASM has been initialized
|
|
35
|
+
*
|
|
36
|
+
* @returns true if WASM setup has been attempted (success or failure)
|
|
37
|
+
*/
|
|
38
|
+
declare function isWasmInitialized(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Reset WASM initialization state (mainly for testing)
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
declare function resetWasmState(): void;
|
|
45
|
+
|
|
46
|
+
export { isWasmInitialized, resetWasmState, setupWasm };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM Setup for AgentShield in Next.js Edge Runtime
|
|
3
|
+
*
|
|
4
|
+
* This module handles WASM initialization for cryptographic signature verification.
|
|
5
|
+
* Designed to work without top-level await to avoid Next.js middleware issues.
|
|
6
|
+
*
|
|
7
|
+
* Usage in middleware.ts:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { setupWasm } from '@kya-os/agentshield-nextjs/wasm-setup';
|
|
10
|
+
* import { createAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';
|
|
11
|
+
*
|
|
12
|
+
* export async function middleware(request: NextRequest) {
|
|
13
|
+
* // Initialize WASM inside the middleware function
|
|
14
|
+
* await setupWasm();
|
|
15
|
+
*
|
|
16
|
+
* const agentShieldMiddleware = createAgentShieldMiddleware({...});
|
|
17
|
+
* return agentShieldMiddleware(request);
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Initialize WASM module for AgentShield
|
|
23
|
+
*
|
|
24
|
+
* This function:
|
|
25
|
+
* - Loads WASM in production/Edge Runtime for cryptographic verification
|
|
26
|
+
* - Skips WASM in test environments (Jest) automatically
|
|
27
|
+
* - Is safe to call multiple times (idempotent)
|
|
28
|
+
* - Handles errors gracefully with fallback to pattern detection
|
|
29
|
+
*
|
|
30
|
+
* @returns Promise that resolves when initialization is complete
|
|
31
|
+
*/
|
|
32
|
+
declare function setupWasm(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Check if WASM has been initialized
|
|
35
|
+
*
|
|
36
|
+
* @returns true if WASM setup has been attempted (success or failure)
|
|
37
|
+
*/
|
|
38
|
+
declare function isWasmInitialized(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Reset WASM initialization state (mainly for testing)
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
declare function resetWasmState(): void;
|
|
45
|
+
|
|
46
|
+
export { isWasmInitialized, resetWasmState, setupWasm };
|