@kya-os/checkpoint-nextjs 1.7.0 → 1.7.1

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.
@@ -2,10 +2,10 @@ import * as _kya_os_checkpoint_wasm_runtime_engine from '@kya-os/checkpoint-wasm
2
2
  import * as _kya_os_checkpoint_wasm_runtime_adapters from '@kya-os/checkpoint-wasm-runtime/adapters';
3
3
  import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
4
4
  import { DetectionReporter, ReporterContext } from '@kya-os/checkpoint-wasm-runtime/reporter';
5
- import { C as CheckpointConfig } from './config-_nfPN3E3.mjs';
5
+ import { C as CheckpointConfig } from './config-DAwIA4DB.mjs';
6
6
  import { ComposedPolicyContext, TrustedDelegationRootsResolver } from './composed-policy.mjs';
7
- import '@kya-os/checkpoint-wasm-runtime/composed-policy';
8
7
  import '@kya-os/checkpoint-shared';
8
+ import '@kya-os/checkpoint-wasm-runtime/composed-policy';
9
9
 
10
10
  /**
11
11
  * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`
@@ -23,7 +23,7 @@ declare function withCheckpoint(config: CheckpointConfig): (req: NextRequest, ev
23
23
  * `package.json` version — pinned by a unit test (the old hardcoded `0.1.0` had
24
24
  * drifted). Re-exported as `VERSION` from the package index.
25
25
  */
26
- declare const VERSION = "1.7.0";
26
+ declare const VERSION = "1.7.1";
27
27
  declare function buildReporter(config: CheckpointConfig, runtime?: 'node' | 'edge'): DetectionReporter | null;
28
28
  /**
29
29
  * Build the per-factory trusted-delegation-roots resolver (P2 / DR-1). Root
@@ -2,10 +2,10 @@ import * as _kya_os_checkpoint_wasm_runtime_engine from '@kya-os/checkpoint-wasm
2
2
  import * as _kya_os_checkpoint_wasm_runtime_adapters from '@kya-os/checkpoint-wasm-runtime/adapters';
3
3
  import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
4
4
  import { DetectionReporter, ReporterContext } from '@kya-os/checkpoint-wasm-runtime/reporter';
5
- import { C as CheckpointConfig } from './config-kxFihzR_.js';
5
+ import { C as CheckpointConfig } from './config-DyU4l5er.js';
6
6
  import { ComposedPolicyContext, TrustedDelegationRootsResolver } from './composed-policy.js';
7
- import '@kya-os/checkpoint-wasm-runtime/composed-policy';
8
7
  import '@kya-os/checkpoint-shared';
8
+ import '@kya-os/checkpoint-wasm-runtime/composed-policy';
9
9
 
10
10
  /**
11
11
  * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`
@@ -23,7 +23,7 @@ declare function withCheckpoint(config: CheckpointConfig): (req: NextRequest, ev
23
23
  * `package.json` version — pinned by a unit test (the old hardcoded `0.1.0` had
24
24
  * drifted). Re-exported as `VERSION` from the package index.
25
25
  */
26
- declare const VERSION = "1.7.0";
26
+ declare const VERSION = "1.7.1";
27
27
  declare function buildReporter(config: CheckpointConfig, runtime?: 'node' | 'edge'): DetectionReporter | null;
28
28
  /**
29
29
  * Build the per-factory trusted-delegation-roots resolver (P2 / DR-1). Root
@@ -100,7 +100,13 @@ function makeComposedPolicyContext(opts) {
100
100
  return structured;
101
101
  }
102
102
  if (outcome.status === "acting") {
103
- return { decision: outcome.engineDecision, acted: true };
103
+ return {
104
+ decision: outcome.engineDecision,
105
+ acted: true,
106
+ // Echo the SSOT revision pin with the decision it produced (#3246
107
+ // PR5). Conditional spread: absent upstream stays absent here.
108
+ ...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
109
+ };
104
110
  }
105
111
  return structured;
106
112
  },
@@ -120,7 +126,12 @@ async function applyComposedPolicy(context, result, path) {
120
126
  if (!context) return;
121
127
  try {
122
128
  const outcome = await context.apply(result, path);
123
- if (outcome.acted) result.decision = outcome.decision;
129
+ if (outcome.acted) {
130
+ result.decision = outcome.decision;
131
+ if (outcome.composedBlobHash) {
132
+ result.composedBlobHash = outcome.composedBlobHash;
133
+ }
134
+ }
124
135
  } catch {
125
136
  }
126
137
  }
@@ -201,12 +212,17 @@ function withCheckpoint(config) {
201
212
  }
202
213
  }
203
214
  await dispatchOnResult(config, result, req);
204
- const rendered = orchestrator.renderDecisionAsResponse(result);
215
+ const bodyReadable200 = checkpointShared.selectBodyReadable200(
216
+ config.delegationChallengeMode ?? "spec-401",
217
+ req.headers,
218
+ result.detectionDetail.detectionClass
219
+ );
220
+ const rendered = orchestrator.renderDecisionAsResponse(result, { bodyReadable200 });
205
221
  return adaptToNextResponse(rendered, req);
206
222
  };
207
223
  }
208
224
  var SDK_NAME = "@kya-os/checkpoint-nextjs";
209
- var VERSION = "1.7.0";
225
+ var VERSION = "1.7.1";
210
226
  function buildReporter(config, runtime = "node") {
211
227
  if (!config.apiKey) return null;
212
228
  return reporter.makeDetectionReporter({
@@ -1,7 +1,7 @@
1
1
  import { verifyRequest, renderDecisionAsResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';
2
2
  import { makeSystemClock, makePolicyEvaluator, makeReputationOracle, makeStatusListCache, makeDidResolver } from '@kya-os/checkpoint-wasm-runtime/adapters';
3
3
  import { makeDetectionReporter } from '@kya-os/checkpoint-wasm-runtime/reporter';
4
- import { PolicyFetcher, requestCarriesDelegationProof, acceptsHtml, encodeVerdictCookie, classifyResponseShape, BLOCKED_PATH, VERDICT_COOKIE_NAME } from '@kya-os/checkpoint-shared';
4
+ import { selectBodyReadable200, PolicyFetcher, requestCarriesDelegationProof, acceptsHtml, encodeVerdictCookie, classifyResponseShape, BLOCKED_PATH, VERDICT_COOKIE_NAME } from '@kya-os/checkpoint-shared';
5
5
  import { NextResponse } from 'next/server';
6
6
  import { makeComposedPolicyCache, evaluateComposedPolicy, verifyResultToAuthorizeInput } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
7
7
 
@@ -98,7 +98,13 @@ function makeComposedPolicyContext(opts) {
98
98
  return structured;
99
99
  }
100
100
  if (outcome.status === "acting") {
101
- return { decision: outcome.engineDecision, acted: true };
101
+ return {
102
+ decision: outcome.engineDecision,
103
+ acted: true,
104
+ // Echo the SSOT revision pin with the decision it produced (#3246
105
+ // PR5). Conditional spread: absent upstream stays absent here.
106
+ ...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
107
+ };
102
108
  }
103
109
  return structured;
104
110
  },
@@ -118,7 +124,12 @@ async function applyComposedPolicy(context, result, path) {
118
124
  if (!context) return;
119
125
  try {
120
126
  const outcome = await context.apply(result, path);
121
- if (outcome.acted) result.decision = outcome.decision;
127
+ if (outcome.acted) {
128
+ result.decision = outcome.decision;
129
+ if (outcome.composedBlobHash) {
130
+ result.composedBlobHash = outcome.composedBlobHash;
131
+ }
132
+ }
122
133
  } catch {
123
134
  }
124
135
  }
@@ -199,12 +210,17 @@ function withCheckpoint(config) {
199
210
  }
200
211
  }
201
212
  await dispatchOnResult(config, result, req);
202
- const rendered = renderDecisionAsResponse(result);
213
+ const bodyReadable200 = selectBodyReadable200(
214
+ config.delegationChallengeMode ?? "spec-401",
215
+ req.headers,
216
+ result.detectionDetail.detectionClass
217
+ );
218
+ const rendered = renderDecisionAsResponse(result, { bodyReadable200 });
203
219
  return adaptToNextResponse(rendered, req);
204
220
  };
205
221
  }
206
222
  var SDK_NAME = "@kya-os/checkpoint-nextjs";
207
- var VERSION = "1.7.0";
223
+ var VERSION = "1.7.1";
208
224
  function buildReporter(config, runtime = "node") {
209
225
  if (!config.apiKey) return null;
210
226
  return makeDetectionReporter({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/checkpoint-nextjs",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Checkpoint Next.js middleware for AI agent detection (formerly @kya-os/agentshield-nextjs)",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -132,8 +132,8 @@
132
132
  "@noble/ed25519": "^2.2.3",
133
133
  "@noble/hashes": "^2.0.1",
134
134
  "@kya-os/checkpoint": "1.2.0",
135
- "@kya-os/checkpoint-shared": "1.2.0",
136
- "@kya-os/checkpoint-wasm-runtime": "^1.8.0"
135
+ "@kya-os/checkpoint-shared": "1.2.1",
136
+ "@kya-os/checkpoint-wasm-runtime": "^1.8.1"
137
137
  },
138
138
  "scripts": {
139
139
  "build": "tsup",
@@ -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,384 +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: 1780362143
38
- // Jun 1, 2026 (exp from OpenAI live directory 2026-05-25)
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;