@kya-os/agentshield-nextjs 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -369
- package/index.js +9 -0
- package/package.json +7 -141
- package/EDGE_RUNTIME_WASM_SETUP.md +0 -348
- package/bin/setup-edge-wasm.js +0 -525
- package/dist/.tsbuildinfo +0 -1
- package/dist/api-client.d.mts +0 -196
- package/dist/api-client.d.ts +0 -196
- package/dist/api-client.js +0 -200
- package/dist/api-client.js.map +0 -1
- package/dist/api-client.mjs +0 -196
- package/dist/api-client.mjs.map +0 -1
- package/dist/api-middleware.d.mts +0 -140
- package/dist/api-middleware.d.ts +0 -140
- package/dist/api-middleware.js +0 -511
- package/dist/api-middleware.js.map +0 -1
- package/dist/api-middleware.mjs +0 -508
- package/dist/api-middleware.mjs.map +0 -1
- package/dist/create-middleware.d.mts +0 -17
- package/dist/create-middleware.d.ts +0 -17
- package/dist/create-middleware.js +0 -1381
- package/dist/create-middleware.js.map +0 -1
- package/dist/create-middleware.mjs +0 -1358
- package/dist/create-middleware.mjs.map +0 -1
- package/dist/edge/index.d.mts +0 -110
- package/dist/edge/index.d.ts +0 -110
- package/dist/edge/index.js +0 -277
- package/dist/edge/index.js.map +0 -1
- package/dist/edge/index.mjs +0 -275
- package/dist/edge/index.mjs.map +0 -1
- package/dist/edge-detector-wrapper.d.mts +0 -34
- package/dist/edge-detector-wrapper.d.ts +0 -34
- package/dist/edge-detector-wrapper.js +0 -596
- package/dist/edge-detector-wrapper.js.map +0 -1
- package/dist/edge-detector-wrapper.mjs +0 -574
- package/dist/edge-detector-wrapper.mjs.map +0 -1
- package/dist/edge-runtime-loader.d.mts +0 -50
- package/dist/edge-runtime-loader.d.ts +0 -50
- package/dist/edge-runtime-loader.js +0 -204
- package/dist/edge-runtime-loader.js.map +0 -1
- package/dist/edge-runtime-loader.mjs +0 -201
- package/dist/edge-runtime-loader.mjs.map +0 -1
- package/dist/edge-wasm-middleware.d.mts +0 -68
- package/dist/edge-wasm-middleware.d.ts +0 -68
- package/dist/edge-wasm-middleware.js +0 -318
- package/dist/edge-wasm-middleware.js.map +0 -1
- package/dist/edge-wasm-middleware.mjs +0 -315
- package/dist/edge-wasm-middleware.mjs.map +0 -1
- package/dist/enhanced-middleware.d.mts +0 -153
- package/dist/enhanced-middleware.d.ts +0 -153
- package/dist/enhanced-middleware.js +0 -1082
- package/dist/enhanced-middleware.js.map +0 -1
- package/dist/enhanced-middleware.mjs +0 -1080
- package/dist/enhanced-middleware.mjs.map +0 -1
- package/dist/index.d.mts +0 -24
- package/dist/index.d.ts +0 -24
- package/dist/index.js +0 -2717
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2662
- package/dist/index.mjs.map +0 -1
- package/dist/middleware.d.mts +0 -21
- package/dist/middleware.d.ts +0 -21
- package/dist/middleware.js +0 -1362
- package/dist/middleware.js.map +0 -1
- package/dist/middleware.mjs +0 -1339
- package/dist/middleware.mjs.map +0 -1
- package/dist/nodejs-wasm-loader.d.mts +0 -25
- package/dist/nodejs-wasm-loader.d.ts +0 -25
- package/dist/nodejs-wasm-loader.js +0 -78
- package/dist/nodejs-wasm-loader.js.map +0 -1
- package/dist/nodejs-wasm-loader.mjs +0 -68
- package/dist/nodejs-wasm-loader.mjs.map +0 -1
- package/dist/policy.d.mts +0 -162
- package/dist/policy.d.ts +0 -162
- package/dist/policy.js +0 -189
- package/dist/policy.js.map +0 -1
- package/dist/policy.mjs +0 -165
- package/dist/policy.mjs.map +0 -1
- package/dist/session-tracker.d.mts +0 -55
- package/dist/session-tracker.d.ts +0 -55
- package/dist/session-tracker.js +0 -170
- package/dist/session-tracker.js.map +0 -1
- package/dist/session-tracker.mjs +0 -167
- package/dist/session-tracker.mjs.map +0 -1
- package/dist/signature-verifier.d.mts +0 -33
- package/dist/signature-verifier.d.ts +0 -33
- package/dist/signature-verifier.js +0 -386
- package/dist/signature-verifier.js.map +0 -1
- package/dist/signature-verifier.mjs +0 -362
- package/dist/signature-verifier.mjs.map +0 -1
- package/dist/types-DVmy9NE3.d.mts +0 -105
- package/dist/types-DVmy9NE3.d.ts +0 -105
- package/dist/wasm-middleware.d.mts +0 -63
- package/dist/wasm-middleware.d.ts +0 -63
- package/dist/wasm-middleware.js +0 -98
- package/dist/wasm-middleware.js.map +0 -1
- package/dist/wasm-middleware.mjs +0 -95
- package/dist/wasm-middleware.mjs.map +0 -1
- package/dist/wasm-setup.d.mts +0 -46
- package/dist/wasm-setup.d.ts +0 -46
- package/dist/wasm-setup.js +0 -157
- package/dist/wasm-setup.js.map +0 -1
- package/dist/wasm-setup.mjs +0 -148
- package/dist/wasm-setup.mjs.map +0 -1
- package/templates/middleware-wasm-100.ts +0 -151
- package/wasm/agentshield_wasm.d.ts +0 -479
- package/wasm/agentshield_wasm.js +0 -1536
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/wasm/package.json +0 -30
- package/wasm.d.ts +0 -21
package/dist/session-tracker.mjs
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { shouldEnforce } from '@kya-os/agentshield-shared';
|
|
2
|
-
|
|
3
|
-
// src/session-tracker.ts
|
|
4
|
-
var EdgeSessionTracker = class {
|
|
5
|
-
config;
|
|
6
|
-
constructor(config) {
|
|
7
|
-
this.config = {
|
|
8
|
-
enabled: config.enabled,
|
|
9
|
-
cookieName: config.cookieName || "__agentshield_session",
|
|
10
|
-
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
11
|
-
// 1 hour default
|
|
12
|
-
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Track a new AI agent session
|
|
17
|
-
*/
|
|
18
|
-
async track(_request, response, result) {
|
|
19
|
-
try {
|
|
20
|
-
if (!this.config.enabled || !shouldEnforce(result)) {
|
|
21
|
-
return response;
|
|
22
|
-
}
|
|
23
|
-
const sessionData = {
|
|
24
|
-
id: crypto.randomUUID(),
|
|
25
|
-
agent: result.detectedAgent?.name || "unknown",
|
|
26
|
-
confidence: result.confidence,
|
|
27
|
-
detectedAt: Date.now(),
|
|
28
|
-
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
29
|
-
};
|
|
30
|
-
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
31
|
-
response.cookies.set(this.config.cookieName, encrypted, {
|
|
32
|
-
httpOnly: true,
|
|
33
|
-
secure: process.env.NODE_ENV === "production",
|
|
34
|
-
sameSite: "lax",
|
|
35
|
-
maxAge: this.config.cookieMaxAge,
|
|
36
|
-
path: "/"
|
|
37
|
-
});
|
|
38
|
-
return response;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
41
|
-
console.warn("AgentShield: Failed to track session:", error);
|
|
42
|
-
}
|
|
43
|
-
return response;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Check for existing AI agent session
|
|
48
|
-
*/
|
|
49
|
-
async check(request) {
|
|
50
|
-
try {
|
|
51
|
-
if (!this.config.enabled) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const cookie = request.cookies.get(this.config.cookieName);
|
|
55
|
-
if (!cookie?.value) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const decrypted = await this.decrypt(cookie.value);
|
|
59
|
-
const session = JSON.parse(decrypted);
|
|
60
|
-
if (session.expires < Date.now()) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return session;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
66
|
-
console.warn("AgentShield: Failed to check session:", error);
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Clear an existing session
|
|
73
|
-
*/
|
|
74
|
-
clear(response) {
|
|
75
|
-
try {
|
|
76
|
-
response.cookies.delete(this.config.cookieName);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
if (process.env.DEBUG_AGENTSHIELD) {
|
|
79
|
-
console.warn("AgentShield: Failed to clear session:", error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return response;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Simple encryption using Web Crypto API (Edge-compatible)
|
|
86
|
-
*/
|
|
87
|
-
async encrypt(data) {
|
|
88
|
-
try {
|
|
89
|
-
const key = this.config.encryptionKey;
|
|
90
|
-
const encoded = new TextEncoder().encode(data);
|
|
91
|
-
const obfuscated = new Uint8Array(encoded.length);
|
|
92
|
-
for (let i = 0; i < encoded.length; i++) {
|
|
93
|
-
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
94
|
-
}
|
|
95
|
-
return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(""));
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return btoa(data);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Simple decryption (Edge-compatible)
|
|
102
|
-
*/
|
|
103
|
-
async decrypt(data) {
|
|
104
|
-
try {
|
|
105
|
-
const key = this.config.encryptionKey;
|
|
106
|
-
const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
107
|
-
const deobfuscated = new Uint8Array(decoded.length);
|
|
108
|
-
for (let i = 0; i < decoded.length; i++) {
|
|
109
|
-
deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
110
|
-
}
|
|
111
|
-
return new TextDecoder().decode(deobfuscated);
|
|
112
|
-
} catch (error) {
|
|
113
|
-
return atob(data);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
var StatelessSessionChecker = class {
|
|
118
|
-
static check(headers) {
|
|
119
|
-
try {
|
|
120
|
-
const agent = headers["kya-session-agent"];
|
|
121
|
-
const confidence = headers["kya-session-confidence"];
|
|
122
|
-
const sessionId = headers["kya-session-id"];
|
|
123
|
-
if (agent && confidence && sessionId) {
|
|
124
|
-
return {
|
|
125
|
-
id: sessionId,
|
|
126
|
-
agent,
|
|
127
|
-
confidence: parseFloat(confidence),
|
|
128
|
-
detectedAt: Date.now(),
|
|
129
|
-
expires: Date.now() + 36e5
|
|
130
|
-
// 1 hour
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const cookieHeader = headers["cookie"];
|
|
134
|
-
if (cookieHeader && cookieHeader.includes("__agentshield_session=")) {
|
|
135
|
-
const match = cookieHeader.match(/__agentshield_session=([^;]+)/);
|
|
136
|
-
if (match && match[1]) {
|
|
137
|
-
try {
|
|
138
|
-
const decoded = atob(match[1]);
|
|
139
|
-
return JSON.parse(decoded);
|
|
140
|
-
} catch {
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
} catch {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
static setHeaders(response, session) {
|
|
150
|
-
try {
|
|
151
|
-
if (response.setHeader) {
|
|
152
|
-
response.setHeader("KYA-Session-Agent", session.agent);
|
|
153
|
-
response.setHeader("KYA-Session-Confidence", session.confidence.toString());
|
|
154
|
-
response.setHeader("KYA-Session-Id", session.id);
|
|
155
|
-
} else if (response.headers && response.headers.set) {
|
|
156
|
-
response.headers.set("kya-session-agent", session.agent);
|
|
157
|
-
response.headers.set("kya-session-confidence", session.confidence.toString());
|
|
158
|
-
response.headers.set("kya-session-id", session.id);
|
|
159
|
-
}
|
|
160
|
-
} catch {
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
export { EdgeSessionTracker, StatelessSessionChecker };
|
|
166
|
-
//# sourceMappingURL=session-tracker.mjs.map
|
|
167
|
-
//# sourceMappingURL=session-tracker.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.mjs","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/agentshield-shared';\nimport { shouldEnforce } from '@kya-os/agentshield-shared';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey || process.env.AGENTSHIELD_SECRET || 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !shouldEnforce(result)) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(''));\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['kya-session-agent'];\n const confidence = headers['kya-session-confidence'];\n const sessionId = headers['kya-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('KYA-Session-Agent', session.agent);\n response.setHeader('KYA-Session-Confidence', session.confidence.toString());\n response.setHeader('KYA-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('kya-session-agent', session.agent);\n response.headers.set('kya-session-confidence', session.confidence.toString());\n response.headers.set('kya-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
* Uses secure URL parsing to prevent spoofing attacks
|
|
30
|
-
*/
|
|
31
|
-
declare function isChatGPTSignature(headers: Record<string, string>): boolean;
|
|
32
|
-
|
|
33
|
-
export { type SignatureVerificationResult, hasSignatureHeaders, isChatGPTSignature, verifyAgentSignature };
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
* Uses secure URL parsing to prevent spoofing attacks
|
|
30
|
-
*/
|
|
31
|
-
declare function isChatGPTSignature(headers: Record<string, string>): boolean;
|
|
32
|
-
|
|
33
|
-
export { type SignatureVerificationResult, hasSignatureHeaders, isChatGPTSignature, verifyAgentSignature };
|
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var ed25519 = require('@noble/ed25519');
|
|
4
|
-
var sha2_js = require('@noble/hashes/sha2.js');
|
|
5
|
-
|
|
6
|
-
function _interopNamespace(e) {
|
|
7
|
-
if (e && e.__esModule) return e;
|
|
8
|
-
var n = Object.create(null);
|
|
9
|
-
if (e) {
|
|
10
|
-
Object.keys(e).forEach(function (k) {
|
|
11
|
-
if (k !== 'default') {
|
|
12
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
-
enumerable: true,
|
|
15
|
-
get: function () { return e[k]; }
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
n.default = e;
|
|
21
|
-
return Object.freeze(n);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
var ed25519__namespace = /*#__PURE__*/_interopNamespace(ed25519);
|
|
25
|
-
|
|
26
|
-
// src/signature-verifier.ts
|
|
27
|
-
ed25519__namespace.etc.sha512Sync = (...m) => sha2_js.sha512(ed25519__namespace.etc.concatBytes(...m));
|
|
28
|
-
var KNOWN_KEYS = {
|
|
29
|
-
chatgpt: [
|
|
30
|
-
{
|
|
31
|
-
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
|
|
32
|
-
// ChatGPT's current Ed25519 public key (base64)
|
|
33
|
-
// Source: https://chatgpt.com/.well-known/http-message-signatures-directory
|
|
34
|
-
publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
|
|
35
|
-
validFrom: 1735689600,
|
|
36
|
-
// Jan 1, 2025 (nbf from OpenAI)
|
|
37
|
-
validUntil: 1769029093
|
|
38
|
-
// Jan 21, 2026 (exp from OpenAI)
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
};
|
|
42
|
-
var keyCache = /* @__PURE__ */ new Map();
|
|
43
|
-
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
44
|
-
var CACHE_MAX_SIZE = 100;
|
|
45
|
-
function getApiBaseUrl() {
|
|
46
|
-
if (typeof window !== "undefined") {
|
|
47
|
-
return "/api/internal";
|
|
48
|
-
}
|
|
49
|
-
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.NEXT_PUBLIC_API_URL || process.env.API_URL || (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);
|
|
50
|
-
if (baseUrl) {
|
|
51
|
-
return baseUrl.replace(/\/$/, "") + "/api/internal";
|
|
52
|
-
}
|
|
53
|
-
if (process.env.NODE_ENV !== "production") {
|
|
54
|
-
console.warn(
|
|
55
|
-
"[Signature] No base URL configured for server-side fetch. Using localhost fallback."
|
|
56
|
-
);
|
|
57
|
-
return "http://localhost:3000/api/internal";
|
|
58
|
-
}
|
|
59
|
-
console.error(
|
|
60
|
-
"[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
|
|
61
|
-
);
|
|
62
|
-
return "/api/internal";
|
|
63
|
-
}
|
|
64
|
-
function cleanupExpiredCache() {
|
|
65
|
-
const now = Date.now();
|
|
66
|
-
const entriesToDelete = [];
|
|
67
|
-
for (const [agent, cached] of keyCache.entries()) {
|
|
68
|
-
if (now - cached.cachedAt > CACHE_TTL_MS) {
|
|
69
|
-
entriesToDelete.push(agent);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const agent of entriesToDelete) {
|
|
73
|
-
keyCache.delete(agent);
|
|
74
|
-
}
|
|
75
|
-
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
76
|
-
const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
|
|
77
|
-
agent,
|
|
78
|
-
cachedAt: cached.cachedAt
|
|
79
|
-
}));
|
|
80
|
-
entries.sort((a, b) => a.cachedAt - b.cachedAt);
|
|
81
|
-
const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
|
|
82
|
-
for (const entry of toRemove) {
|
|
83
|
-
keyCache.delete(entry.agent);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function fetchKeysFromApi(agent) {
|
|
88
|
-
if (keyCache.size > CACHE_MAX_SIZE) {
|
|
89
|
-
cleanupExpiredCache();
|
|
90
|
-
}
|
|
91
|
-
const cached = keyCache.get(agent);
|
|
92
|
-
if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
|
|
93
|
-
return cached.keys;
|
|
94
|
-
}
|
|
95
|
-
if (typeof fetch === "undefined") {
|
|
96
|
-
console.warn("[Signature] fetch() not available in this environment");
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
const apiBaseUrl = getApiBaseUrl();
|
|
101
|
-
const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
|
|
102
|
-
const response = await fetch(url, {
|
|
103
|
-
method: "GET",
|
|
104
|
-
headers: {
|
|
105
|
-
"Content-Type": "application/json"
|
|
106
|
-
},
|
|
107
|
-
// 5 second timeout
|
|
108
|
-
signal: AbortSignal.timeout(5e3)
|
|
109
|
-
});
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const data = await response.json();
|
|
115
|
-
if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
|
|
116
|
-
console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
keyCache.set(agent, {
|
|
120
|
-
keys: data.keys,
|
|
121
|
-
cachedAt: Date.now()
|
|
122
|
-
});
|
|
123
|
-
return data.keys;
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.warn("[Signature] Error fetching keys from API, using fallback", {
|
|
126
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
127
|
-
agent
|
|
128
|
-
});
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function isValidAgent(agent) {
|
|
133
|
-
return agent in KNOWN_KEYS;
|
|
134
|
-
}
|
|
135
|
-
async function getKeysForAgent(agent) {
|
|
136
|
-
const apiKeys = await fetchKeysFromApi(agent);
|
|
137
|
-
if (apiKeys && apiKeys.length > 0) {
|
|
138
|
-
return apiKeys;
|
|
139
|
-
}
|
|
140
|
-
if (isValidAgent(agent)) {
|
|
141
|
-
return KNOWN_KEYS[agent];
|
|
142
|
-
}
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
function parseSignatureInput(signatureInput) {
|
|
146
|
-
try {
|
|
147
|
-
const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
|
|
148
|
-
if (!match) return null;
|
|
149
|
-
const [, headersList, params] = match;
|
|
150
|
-
const signedHeaders = headersList ? headersList.split(" ").map((h) => h.replace(/"/g, "").trim()).filter((h) => h.length > 0) : [];
|
|
151
|
-
const keyidMatch = params ? params.match(/keyid="([^"]+)"/) : null;
|
|
152
|
-
const createdMatch = params ? params.match(/created=(\d+)/) : null;
|
|
153
|
-
const expiresMatch = params ? params.match(/expires=(\d+)/) : null;
|
|
154
|
-
if (!keyidMatch || !keyidMatch[1]) return null;
|
|
155
|
-
return {
|
|
156
|
-
keyid: keyidMatch[1],
|
|
157
|
-
created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : void 0,
|
|
158
|
-
expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : void 0,
|
|
159
|
-
signedHeaders
|
|
160
|
-
};
|
|
161
|
-
} catch (error) {
|
|
162
|
-
console.error("[Signature] Failed to parse Signature-Input:", error);
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function buildSignatureBase(method, path, headers, signedHeaders) {
|
|
167
|
-
const components = [];
|
|
168
|
-
for (const headerName of signedHeaders) {
|
|
169
|
-
let value;
|
|
170
|
-
switch (headerName) {
|
|
171
|
-
case "@method":
|
|
172
|
-
value = method.toUpperCase();
|
|
173
|
-
break;
|
|
174
|
-
case "@path":
|
|
175
|
-
value = path;
|
|
176
|
-
break;
|
|
177
|
-
case "@authority":
|
|
178
|
-
value = headers["host"] || headers["Host"] || "";
|
|
179
|
-
break;
|
|
180
|
-
default: {
|
|
181
|
-
const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
|
|
182
|
-
value = key ? headers[key] || "" : "";
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
components.push(`"${headerName}": ${value}`);
|
|
187
|
-
}
|
|
188
|
-
return components.join("\n");
|
|
189
|
-
}
|
|
190
|
-
function base64ToBytes(base64) {
|
|
191
|
-
let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
192
|
-
const padding = standardBase64.length % 4;
|
|
193
|
-
if (padding) {
|
|
194
|
-
standardBase64 += "=".repeat(4 - padding);
|
|
195
|
-
}
|
|
196
|
-
const binaryString = atob(standardBase64);
|
|
197
|
-
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
198
|
-
}
|
|
199
|
-
async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
|
|
200
|
-
try {
|
|
201
|
-
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
202
|
-
const signatureBytes = base64ToBytes(signatureBase64);
|
|
203
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
204
|
-
if (publicKeyBytes.length !== 32) {
|
|
205
|
-
console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
if (signatureBytes.length !== 64) {
|
|
209
|
-
console.error("[Signature] Invalid signature length:", signatureBytes.length);
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
return ed25519__namespace.verify(signatureBytes, messageBytes, publicKeyBytes);
|
|
213
|
-
} catch (nobleError) {
|
|
214
|
-
console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
|
|
215
|
-
try {
|
|
216
|
-
const publicKeyBytes = base64ToBytes(publicKeyBase64);
|
|
217
|
-
const signatureBytes = base64ToBytes(signatureBase64);
|
|
218
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
219
|
-
const publicKey = await crypto.subtle.importKey(
|
|
220
|
-
"raw",
|
|
221
|
-
publicKeyBytes.buffer,
|
|
222
|
-
{
|
|
223
|
-
name: "Ed25519",
|
|
224
|
-
namedCurve: "Ed25519"
|
|
225
|
-
},
|
|
226
|
-
false,
|
|
227
|
-
["verify"]
|
|
228
|
-
);
|
|
229
|
-
return await crypto.subtle.verify(
|
|
230
|
-
"Ed25519",
|
|
231
|
-
publicKey,
|
|
232
|
-
signatureBytes.buffer,
|
|
233
|
-
messageBytes
|
|
234
|
-
);
|
|
235
|
-
} catch (cryptoError) {
|
|
236
|
-
console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
|
|
237
|
-
nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
|
|
238
|
-
cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
|
|
239
|
-
});
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
async function verifyAgentSignature(method, path, headers) {
|
|
245
|
-
const signature = headers["signature"] || headers["Signature"];
|
|
246
|
-
const signatureInput = headers["signature-input"] || headers["Signature-Input"];
|
|
247
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
248
|
-
if (!signature || !signatureInput) {
|
|
249
|
-
return {
|
|
250
|
-
isValid: false,
|
|
251
|
-
confidence: 0,
|
|
252
|
-
reason: "No signature headers present",
|
|
253
|
-
verificationMethod: "none"
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
const parsed = parseSignatureInput(signatureInput);
|
|
257
|
-
if (!parsed) {
|
|
258
|
-
return {
|
|
259
|
-
isValid: false,
|
|
260
|
-
confidence: 0,
|
|
261
|
-
reason: "Invalid Signature-Input header",
|
|
262
|
-
verificationMethod: "none"
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
if (parsed.created) {
|
|
266
|
-
const now2 = Math.floor(Date.now() / 1e3);
|
|
267
|
-
const age = now2 - parsed.created;
|
|
268
|
-
if (age > 300) {
|
|
269
|
-
return {
|
|
270
|
-
isValid: false,
|
|
271
|
-
confidence: 0,
|
|
272
|
-
reason: "Signature expired (older than 5 minutes)",
|
|
273
|
-
verificationMethod: "none"
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
if (age < -30) {
|
|
277
|
-
return {
|
|
278
|
-
isValid: false,
|
|
279
|
-
confidence: 0,
|
|
280
|
-
reason: "Signature timestamp is in the future",
|
|
281
|
-
verificationMethod: "none"
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
let agent;
|
|
286
|
-
let agentKey;
|
|
287
|
-
const isChatGPT = signatureAgent === '"https://chatgpt.com"' || (() => {
|
|
288
|
-
try {
|
|
289
|
-
const url = new URL(signatureAgent?.replace(/^"|"$/g, "") || "");
|
|
290
|
-
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
291
|
-
} catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
})();
|
|
295
|
-
if (isChatGPT) {
|
|
296
|
-
agent = "ChatGPT";
|
|
297
|
-
agentKey = "chatgpt";
|
|
298
|
-
}
|
|
299
|
-
if (!agent || !agentKey) {
|
|
300
|
-
return {
|
|
301
|
-
isValid: false,
|
|
302
|
-
confidence: 0,
|
|
303
|
-
reason: "Unknown signature agent",
|
|
304
|
-
verificationMethod: "none"
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
const knownKeys = await getKeysForAgent(agentKey);
|
|
308
|
-
if (knownKeys.length === 0) {
|
|
309
|
-
return {
|
|
310
|
-
isValid: false,
|
|
311
|
-
confidence: 0,
|
|
312
|
-
reason: "No keys available for agent",
|
|
313
|
-
verificationMethod: "none"
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const key = knownKeys.find((k) => k.kid === parsed.keyid);
|
|
317
|
-
if (!key) {
|
|
318
|
-
return {
|
|
319
|
-
isValid: false,
|
|
320
|
-
confidence: 0,
|
|
321
|
-
reason: `Unknown key ID: ${parsed.keyid}`,
|
|
322
|
-
verificationMethod: "none"
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
326
|
-
if (now < key.validFrom || now > key.validUntil) {
|
|
327
|
-
return {
|
|
328
|
-
isValid: false,
|
|
329
|
-
confidence: 0,
|
|
330
|
-
reason: "Key is not valid at current time",
|
|
331
|
-
verificationMethod: "none"
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);
|
|
335
|
-
let signatureValue = signature;
|
|
336
|
-
if (signatureValue.startsWith("sig1=:")) {
|
|
337
|
-
signatureValue = signatureValue.substring(6);
|
|
338
|
-
}
|
|
339
|
-
if (signatureValue.endsWith(":")) {
|
|
340
|
-
signatureValue = signatureValue.slice(0, -1);
|
|
341
|
-
}
|
|
342
|
-
const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
|
|
343
|
-
if (isValid) {
|
|
344
|
-
return {
|
|
345
|
-
isValid: true,
|
|
346
|
-
agent,
|
|
347
|
-
keyid: parsed.keyid,
|
|
348
|
-
confidence: 1,
|
|
349
|
-
// 100% confidence for valid signature
|
|
350
|
-
verificationMethod: "signature"
|
|
351
|
-
};
|
|
352
|
-
} else {
|
|
353
|
-
return {
|
|
354
|
-
isValid: false,
|
|
355
|
-
confidence: 0,
|
|
356
|
-
reason: "Signature verification failed",
|
|
357
|
-
verificationMethod: "none"
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function hasSignatureHeaders(headers) {
|
|
362
|
-
return !!((headers["signature"] || headers["Signature"]) && (headers["signature-input"] || headers["Signature-Input"]));
|
|
363
|
-
}
|
|
364
|
-
function isChatGPTSignature(headers) {
|
|
365
|
-
const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
|
|
366
|
-
if (!signatureAgent) {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
|
|
370
|
-
if (agentUrlStr === "https://chatgpt.com") {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
try {
|
|
374
|
-
const agentUrl = new URL(agentUrlStr);
|
|
375
|
-
const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
|
|
376
|
-
return allowedHosts.includes(agentUrl.host);
|
|
377
|
-
} catch {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
exports.hasSignatureHeaders = hasSignatureHeaders;
|
|
383
|
-
exports.isChatGPTSignature = isChatGPTSignature;
|
|
384
|
-
exports.verifyAgentSignature = verifyAgentSignature;
|
|
385
|
-
//# sourceMappingURL=signature-verifier.js.map
|
|
386
|
-
//# sourceMappingURL=signature-verifier.js.map
|