@astrasyncai/verification-gateway 3.9.0 → 3.10.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.
Files changed (108) hide show
  1. package/dist/adapter-interface/interface.d.mts +3 -2
  2. package/dist/adapter-interface/interface.d.ts +3 -2
  3. package/dist/adapters/express.d.mts +3 -2
  4. package/dist/adapters/express.d.ts +3 -2
  5. package/dist/adapters/express.js +151 -2
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +151 -2
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +2 -1
  10. package/dist/adapters/mcp.d.ts +2 -1
  11. package/dist/adapters/mcp.js +1 -1
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +1 -1
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +3 -2
  16. package/dist/adapters/nextjs.d.ts +3 -2
  17. package/dist/adapters/nextjs.js +151 -2
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +151 -2
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +3 -2
  22. package/dist/adapters/sdk.d.ts +3 -2
  23. package/dist/adapters/sdk.js +1 -1
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +1 -1
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +3 -2
  28. package/dist/agent/index.d.ts +3 -2
  29. package/dist/bin/astrasync-claude-hook.js +276 -25
  30. package/dist/bin/astrasync-guard.js +258 -23
  31. package/dist/bin/astrasync.js +282 -24
  32. package/dist/browser/background.js +257 -22
  33. package/dist/browser/background.js.map +1 -1
  34. package/dist/browser/background.mjs +257 -22
  35. package/dist/browser/background.mjs.map +1 -1
  36. package/dist/browser/browser-adapter.d.mts +3 -2
  37. package/dist/browser/browser-adapter.d.ts +3 -2
  38. package/dist/claude-code/claude-code-adapter.d.mts +3 -2
  39. package/dist/claude-code/claude-code-adapter.d.ts +3 -2
  40. package/dist/cli/index.d.mts +3 -2
  41. package/dist/cli/index.d.ts +3 -2
  42. package/dist/cursor/cursor-adapter.d.mts +3 -2
  43. package/dist/cursor/cursor-adapter.d.ts +3 -2
  44. package/dist/cursor/extension.d.mts +3 -2
  45. package/dist/cursor/extension.d.ts +3 -2
  46. package/dist/cursor/extension.js +257 -22
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +257 -22
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +2 -1
  51. package/dist/edge-config.d.ts +2 -1
  52. package/dist/{express-sGXhDdd0.d.ts → express-ClNtDhhc.d.ts} +1 -1
  53. package/dist/{express-DXUyvv7t.d.mts → express-DCXa1oqj.d.mts} +1 -1
  54. package/dist/gateway/gateway.d.mts +29 -3
  55. package/dist/gateway/gateway.d.ts +29 -3
  56. package/dist/gateway/gateway.js +257 -22
  57. package/dist/gateway/gateway.js.map +1 -1
  58. package/dist/gateway/gateway.mjs +257 -22
  59. package/dist/gateway/gateway.mjs.map +1 -1
  60. package/dist/git-trigger/git-hooks.d.mts +3 -2
  61. package/dist/git-trigger/git-hooks.d.ts +3 -2
  62. package/dist/{index-Dalih3EB.d.mts → index-BrNKDmiR.d.mts} +1 -1
  63. package/dist/{index-C61VL_OW.d.ts → index-CfHurO1P.d.ts} +1 -1
  64. package/dist/{index-CMg-vjEK.d.ts → index-D2ztkumS.d.ts} +1 -1
  65. package/dist/{index-DmmdehNu.d.mts → index-Dr_XiN65.d.mts} +1 -1
  66. package/dist/index.d.mts +10 -9
  67. package/dist/index.d.ts +10 -9
  68. package/dist/index.js +218 -11
  69. package/dist/index.js.map +1 -1
  70. package/dist/index.mjs +211 -11
  71. package/dist/index.mjs.map +1 -1
  72. package/dist/local-evaluator/evaluator.d.mts +3 -2
  73. package/dist/local-evaluator/evaluator.d.ts +3 -2
  74. package/dist/local-evaluator/evaluator.js +165 -7
  75. package/dist/local-evaluator/evaluator.js.map +1 -1
  76. package/dist/local-evaluator/evaluator.mjs +165 -7
  77. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  78. package/dist/metadata-capture.d.mts +87 -0
  79. package/dist/metadata-capture.d.ts +87 -0
  80. package/dist/metadata-capture.js +184 -0
  81. package/dist/metadata-capture.js.map +1 -0
  82. package/dist/metadata-capture.mjs +154 -0
  83. package/dist/metadata-capture.mjs.map +1 -0
  84. package/dist/{nextjs-DwUW-Nm1.d.ts → nextjs-CEBVBsdi.d.ts} +1 -1
  85. package/dist/{nextjs-F_2pbPIA.d.mts → nextjs-DcQIk6Zj.d.mts} +1 -1
  86. package/dist/platform-signatures.d.mts +60 -10
  87. package/dist/platform-signatures.d.ts +60 -10
  88. package/dist/platform-signatures.js +37 -10
  89. package/dist/platform-signatures.js.map +1 -1
  90. package/dist/platform-signatures.mjs +35 -9
  91. package/dist/platform-signatures.mjs.map +1 -1
  92. package/dist/{sdk-Clob2-0A.d.mts → sdk-CZkbSOeO.d.mts} +1 -1
  93. package/dist/{sdk-CfHANs4A.d.ts → sdk-H0JCLW2y.d.ts} +1 -1
  94. package/dist/transport/index.d.mts +3 -2
  95. package/dist/transport/index.d.ts +3 -2
  96. package/dist/{types-ACMsV07B.d.mts → types-9tns4wyl.d.ts} +12 -0
  97. package/dist/{types-DmSDgPpG.d.ts → types-BV6abBnF.d.mts} +7 -1
  98. package/dist/{types-BtqUvK8v.d.mts → types-C23_uaY8.d.ts} +7 -1
  99. package/dist/{types-ACMsV07B.d.ts → types-sy3tq8lp.d.mts} +12 -0
  100. package/dist/ui/index.d.mts +2 -1
  101. package/dist/ui/index.d.ts +2 -1
  102. package/dist/verify.d.mts +8 -1
  103. package/dist/verify.d.ts +8 -1
  104. package/dist/verify.js +1 -1
  105. package/dist/verify.js.map +1 -1
  106. package/dist/verify.mjs +1 -1
  107. package/dist/verify.mjs.map +1 -1
  108. package/package.json +6 -1
@@ -0,0 +1,154 @@
1
+ // src/metadata-capture.ts
2
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
3
+ "cookie",
4
+ "set-cookie",
5
+ "x-csrf-token",
6
+ "proxy-authorization",
7
+ "x-astrasync-signature",
8
+ "x-astrasync-secret",
9
+ "x-hub-signature",
10
+ "x-hub-signature-256"
11
+ ]);
12
+ var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
13
+ var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
14
+ "authorization",
15
+ "x-api-key",
16
+ "x-goog-api-key",
17
+ "api-key"
18
+ ]);
19
+ function isSignatureHeader(name) {
20
+ return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
21
+ }
22
+ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
23
+ "openai-organization",
24
+ "openai-project",
25
+ "openai-version",
26
+ "openai-processing-ms",
27
+ "anthropic-version",
28
+ "anthropic-beta",
29
+ "x-goog-user-project",
30
+ "x-goog-api-client",
31
+ "user-agent",
32
+ "x-agent-card-url",
33
+ "x-agent-provider"
34
+ ]);
35
+ var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
36
+ var KEY_FORMAT_PREFIXES = [
37
+ "sk-ant-api03",
38
+ "sk-ant-api",
39
+ "sk-ant",
40
+ "sk-proj",
41
+ "sk-svcacct",
42
+ "sk-or-v1",
43
+ "sk-or",
44
+ "sk-lf",
45
+ "sk-",
46
+ "gsk_",
47
+ "aiza",
48
+ "ya29",
49
+ "ghp_",
50
+ "gho_",
51
+ "github_pat_",
52
+ "xai-",
53
+ "pplx-",
54
+ "r8_",
55
+ "hf_",
56
+ "astra-",
57
+ "astrae-"
58
+ ];
59
+ var MAX_HEADERS = 64;
60
+ var MAX_HEADER_VALUE_BYTES = 1024;
61
+ var MAX_TOTAL_BYTES = 16 * 1024;
62
+ function byteLength(s) {
63
+ let bytes = 0;
64
+ for (let i = 0; i < s.length; i++) {
65
+ const code = s.charCodeAt(i);
66
+ if (code < 128) bytes += 1;
67
+ else if (code < 2048) bytes += 2;
68
+ else if (code >= 55296 && code <= 56319) {
69
+ bytes += 4;
70
+ i++;
71
+ } else bytes += 3;
72
+ }
73
+ return bytes;
74
+ }
75
+ function extractApiKeyFormat(rawValue) {
76
+ if (!rawValue) return void 0;
77
+ let value = rawValue.trim();
78
+ const spaceIdx = value.indexOf(" ");
79
+ if (spaceIdx > 0) {
80
+ const scheme = value.slice(0, spaceIdx).toLowerCase();
81
+ if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
82
+ value = value.slice(spaceIdx + 1).trim();
83
+ }
84
+ }
85
+ const lower = value.toLowerCase();
86
+ for (const prefix of KEY_FORMAT_PREFIXES) {
87
+ if (lower.startsWith(prefix)) {
88
+ return prefix;
89
+ }
90
+ }
91
+ return void 0;
92
+ }
93
+ function isSecretHeader(name) {
94
+ if (SECRET_HEADER_NAMES.has(name)) return true;
95
+ if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
96
+ if (isSignatureHeader(name)) return true;
97
+ return false;
98
+ }
99
+ function isPlatformHeader(name) {
100
+ if (PLATFORM_HEADER_NAMES.has(name)) return true;
101
+ return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
102
+ }
103
+ function sanitizeHeaders(raw) {
104
+ const headers = {};
105
+ const platformHeaders = {};
106
+ let apiKeyFormat;
107
+ let totalBytes = 0;
108
+ let count = 0;
109
+ if (!raw) return { headers };
110
+ for (const rawName of Object.keys(raw)) {
111
+ if (count >= MAX_HEADERS) break;
112
+ const name = rawName.toLowerCase();
113
+ const rawValue = raw[rawName];
114
+ if (rawValue === void 0) continue;
115
+ const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
116
+ if (CREDENTIAL_HEADER_NAMES.has(name)) {
117
+ const fmt = extractApiKeyFormat(joined);
118
+ if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
119
+ continue;
120
+ }
121
+ if (isSecretHeader(name)) continue;
122
+ let value = joined;
123
+ if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
124
+ value = value.slice(0, MAX_HEADER_VALUE_BYTES);
125
+ }
126
+ const entryBytes = byteLength(name) + byteLength(value);
127
+ if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
128
+ totalBytes += entryBytes;
129
+ count++;
130
+ headers[name] = value;
131
+ if (isPlatformHeader(name)) platformHeaders[name] = value;
132
+ }
133
+ const result = { headers };
134
+ if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
135
+ if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
136
+ return result;
137
+ }
138
+ function extractPlatformHeaders(sanitized) {
139
+ const out = {};
140
+ if (!sanitized) return out;
141
+ for (const name of Object.keys(sanitized)) {
142
+ if (isPlatformHeader(name.toLowerCase())) out[name.toLowerCase()] = sanitized[name];
143
+ }
144
+ return out;
145
+ }
146
+ export {
147
+ MAX_HEADERS,
148
+ MAX_HEADER_VALUE_BYTES,
149
+ MAX_TOTAL_BYTES,
150
+ extractApiKeyFormat,
151
+ extractPlatformHeaders,
152
+ sanitizeHeaders
153
+ };
154
+ //# sourceMappingURL=metadata-capture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/metadata-capture.ts"],"sourcesContent":["/**\n * Maximal metadata capture — the shared, edge-safe sanitiser.\n *\n * AstraSync's thesis is total metadata capture: every header and connection\n * signal an inbound agent presents is a signal we want to keep. This module is\n * the ONE place that decides what is safe to store verbatim, what is a secret\n * to drop, and what credential headers reduce to a safe *format prefix* (a\n * platform signal, never the secret itself).\n *\n * It runs in three places that must agree byte-for-byte:\n * - the edge adapter (`@astrasyncai/adapter-lambda`) — the richest capture\n * point (full CloudFront header arrays),\n * - the SDK adapters (express / nextjs) — for SDK-only merchants with no edge,\n * - (indirectly) the backend, which stores whatever the above forward.\n *\n * Pure — no Node built-ins, no I/O — so it is safe in Lambda@Edge, the browser\n * build, and Deno. Capture is cheap: at the edge and SDK it is local string\n * work, and the backend stores it on a fire-and-forget event insert, so it adds\n * ZERO verify-access decision latency. Detection (which few signals imply which\n * vendor) is a separate, curated concern — see `platform-signatures.ts`.\n */\n\n/**\n * The captured, sanitised view of an inbound agent's request metadata. Attached\n * to verify-access / beacon payloads as `callerMetadata.observedMetadata`.\n */\nexport interface ObservedMetadata {\n /** Header name → value, secrets removed, verbatim otherwise. */\n headers: Record<string, string>;\n /**\n * Safe key-format prefix of a credential header (e.g. `sk-ant-api03`,\n * `sk-proj`, `AIza`) — a platform signal, NEVER the secret. Absent if no\n * credential header was present or none matched a known format.\n */\n apiKeyFormat?: string;\n /**\n * The subset of `headers` that are known platform-signal headers\n * (`openai-organization`, `x-goog-user-project`, `anthropic-version`, …),\n * pulled out for convenient detection + display. Values are the same\n * sanitised strings as in `headers`.\n */\n platformHeaders?: Record<string, string>;\n /**\n * Connection-layer signals the edge can see but the SDK cannot (IP, ASN,\n * country, TLS version/cipher, HTTP version, device class). Populated by the\n * edge adapter's `collectConnectionMetadata`; absent for SDK-only capture.\n */\n connection?: Record<string, string>;\n}\n\n/**\n * Result of sanitising a raw header map. `headers` is always present (possibly\n * empty); `apiKeyFormat` / `platformHeaders` only when a signal was found.\n */\nexport interface SanitizeHeadersResult {\n headers: Record<string, string>;\n apiKeyFormat?: string;\n platformHeaders?: Record<string, string>;\n}\n\n/**\n * Header names dropped entirely — genuine secrets or our own credentials that\n * describe the request's transport, not the agent. Matched case-insensitively\n * on the exact header name.\n */\nconst SECRET_HEADER_NAMES = new Set([\n 'cookie',\n 'set-cookie',\n 'x-csrf-token',\n 'proxy-authorization',\n 'x-astrasync-signature',\n 'x-astrasync-secret',\n 'x-hub-signature',\n 'x-hub-signature-256',\n]);\n\n/**\n * Header-name prefixes dropped entirely — our own credential / signing surface\n * (`x-astrasync-*`, `kya-*`) must never round-trip into stored metadata.\n */\nconst SECRET_HEADER_PREFIXES = ['x-astrasync-', 'kya-', 'kya_'];\n\n/**\n * Credential headers whose VALUE is a secret but whose FORMAT is a signal. We\n * keep the leading format prefix (up to the random tail) and drop the rest.\n */\nconst CREDENTIAL_HEADER_NAMES = new Set([\n 'authorization',\n 'x-api-key',\n 'x-goog-api-key',\n 'api-key',\n]);\n\n/**\n * Header names that carry a raw signature value (drop the value; the presence\n * is not worth a bloat risk). Matched as a suffix so `x-*-signature` variants\n * are covered without an exhaustive list.\n */\nfunction isSignatureHeader(name: string): boolean {\n return name === 'signature' || name.endsWith('-signature') || name.endsWith('-signature-256');\n}\n\n/**\n * Known platform-signal headers — pulled into `platformHeaders` for convenient\n * detection + display. Exact names plus a few prefixes (`x-goog-`,\n * `x-stainless-` — the OpenAI/Anthropic SDK client fingerprint headers).\n */\nconst PLATFORM_HEADER_NAMES = new Set([\n 'openai-organization',\n 'openai-project',\n 'openai-version',\n 'openai-processing-ms',\n 'anthropic-version',\n 'anthropic-beta',\n 'x-goog-user-project',\n 'x-goog-api-client',\n 'user-agent',\n 'x-agent-card-url',\n 'x-agent-provider',\n]);\nconst PLATFORM_HEADER_PREFIXES = ['x-goog-', 'x-stainless-', 'x-anthropic-', 'x-openai-'];\n\n/**\n * Known API-key format prefixes, longest-first so `sk-ant-api03` wins over\n * `sk-ant` and `sk-`. Each is the SAFE, non-secret leading token of a\n * credential; the random tail is never captured.\n */\nconst KEY_FORMAT_PREFIXES = [\n 'sk-ant-api03',\n 'sk-ant-api',\n 'sk-ant',\n 'sk-proj',\n 'sk-svcacct',\n 'sk-or-v1',\n 'sk-or',\n 'sk-lf',\n 'sk-',\n 'gsk_',\n 'aiza',\n 'ya29',\n 'ghp_',\n 'gho_',\n 'github_pat_',\n 'xai-',\n 'pplx-',\n 'r8_',\n 'hf_',\n 'astra-',\n 'astrae-',\n];\n\n/** Size caps — the only guard against jsonb bloat from a hostile POST. */\nexport const MAX_HEADERS = 64;\nexport const MAX_HEADER_VALUE_BYTES = 1024;\nexport const MAX_TOTAL_BYTES = 16 * 1024;\n\nfunction byteLength(s: string): number {\n // Edge-safe (no Buffer): count UTF-8 bytes without allocating a Buffer.\n let bytes = 0;\n for (let i = 0; i < s.length; i++) {\n const code = s.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++; // surrogate pair\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Extract the safe key-format prefix from a credential header value. Strips a\n * leading `Bearer ` / `Basic ` scheme, then returns the longest known prefix\n * that the (lowercased) value starts with. Returns undefined for opaque values\n * (e.g. a bare UUID token) so we never guess a format we don't recognise.\n */\nexport function extractApiKeyFormat(rawValue: string): string | undefined {\n if (!rawValue) return undefined;\n let value = rawValue.trim();\n const spaceIdx = value.indexOf(' ');\n if (spaceIdx > 0) {\n const scheme = value.slice(0, spaceIdx).toLowerCase();\n if (scheme === 'bearer' || scheme === 'basic' || scheme === 'token') {\n value = value.slice(spaceIdx + 1).trim();\n }\n }\n const lower = value.toLowerCase();\n for (const prefix of KEY_FORMAT_PREFIXES) {\n if (lower.startsWith(prefix)) {\n // Return the prefix as-declared in the list (canonical casing).\n return prefix;\n }\n }\n return undefined;\n}\n\nfunction isSecretHeader(name: string): boolean {\n if (SECRET_HEADER_NAMES.has(name)) return true;\n if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;\n if (isSignatureHeader(name)) return true;\n return false;\n}\n\nfunction isPlatformHeader(name: string): boolean {\n if (PLATFORM_HEADER_NAMES.has(name)) return true;\n return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));\n}\n\n/**\n * Sanitise a raw header map for storage. Deny-lists genuine secrets, reduces\n * credential headers to a safe format prefix, keeps everything else verbatim,\n * and enforces the size caps that bound jsonb growth.\n *\n * Accepts any string→(string|string[]|undefined) map (Node's `req.headers`,\n * CloudFront's flattened headers, a plain object). Multi-value headers are\n * joined with `, ` per RFC 7230.\n */\nexport function sanitizeHeaders(\n raw: Record<string, string | string[] | undefined> | undefined | null\n): SanitizeHeadersResult {\n const headers: Record<string, string> = {};\n const platformHeaders: Record<string, string> = {};\n let apiKeyFormat: string | undefined;\n let totalBytes = 0;\n let count = 0;\n\n if (!raw) return { headers };\n\n for (const rawName of Object.keys(raw)) {\n if (count >= MAX_HEADERS) break;\n const name = rawName.toLowerCase();\n const rawValue = raw[rawName];\n if (rawValue === undefined) continue;\n const joined = Array.isArray(rawValue) ? rawValue.join(', ') : String(rawValue);\n\n // Credential headers: capture the format prefix, drop the secret.\n if (CREDENTIAL_HEADER_NAMES.has(name)) {\n const fmt = extractApiKeyFormat(joined);\n if (fmt && !apiKeyFormat) apiKeyFormat = fmt;\n continue;\n }\n\n // Genuine secrets: drop entirely.\n if (isSecretHeader(name)) continue;\n\n // Everything else describing the agent: keep, bounded.\n let value = joined;\n if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {\n value = value.slice(0, MAX_HEADER_VALUE_BYTES);\n }\n const entryBytes = byteLength(name) + byteLength(value);\n if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;\n totalBytes += entryBytes;\n count++;\n headers[name] = value;\n if (isPlatformHeader(name)) platformHeaders[name] = value;\n }\n\n const result: SanitizeHeadersResult = { headers };\n if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;\n if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;\n return result;\n}\n\n/**\n * Pull the known platform-signal headers out of an ALREADY-sanitised header\n * map. `sanitizeHeaders` already computes this as a by-product; this standalone\n * form is exported for call-sites (edge, tests) that hold a sanitised map and\n * want only the platform subset. Never re-run over raw (unsanitised) headers.\n */\nexport function extractPlatformHeaders(\n sanitized: Record<string, string> | undefined | null\n): Record<string, string> {\n const out: Record<string, string> = {};\n if (!sanitized) return out;\n for (const name of Object.keys(sanitized)) {\n if (isPlatformHeader(name.toLowerCase())) out[name.toLowerCase()] = sanitized[name];\n }\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import * as next_server from 'next/server';
2
2
  import { NextRequest } from 'next/server';
3
- import { N as NextJsMiddlewareOptions } from './types-ACMsV07B.js';
3
+ import { N as NextJsMiddlewareOptions } from './types-9tns4wyl.js';
4
4
 
5
5
  /**
6
6
  * Create Next.js middleware for agent verification.
@@ -1,6 +1,6 @@
1
1
  import * as next_server from 'next/server';
2
2
  import { NextRequest } from 'next/server';
3
- import { N as NextJsMiddlewareOptions } from './types-ACMsV07B.mjs';
3
+ import { N as NextJsMiddlewareOptions } from './types-sy3tq8lp.mjs';
4
4
 
5
5
  /**
6
6
  * Create Next.js middleware for agent verification.
@@ -13,7 +13,7 @@
13
13
  * v2.9 — TLS fingerprinting / behavioural signatures are deferred
14
14
  * (recorded in adapter requirements doc).
15
15
  */
16
- type PlatformAgentVendor = 'claude' | 'chatgpt' | 'gemini' | 'cursor' | 'goose' | 'unknown';
16
+ type PlatformAgentVendor = 'claude' | 'chatgpt' | 'gemini' | 'cursor' | 'goose' | 'perplexity' | 'unknown';
17
17
  interface PlatformFingerprint {
18
18
  vendor: PlatformAgentVendor;
19
19
  /** A stable identifier used as the orphan-agent dedup key. */
@@ -24,6 +24,51 @@ interface PlatformFingerprint {
24
24
  interface PlatformDetectionInput {
25
25
  userAgent?: string;
26
26
  agentCardUrl?: string;
27
+ /**
28
+ * Workstream D — a safe API-key format prefix observed on the caller's
29
+ * credential header (e.g. `sk-ant-api03`, `sk-proj`, `aiza`). A strong
30
+ * platform signal; never the secret. Matched against `apiKeyFormatPatterns`.
31
+ */
32
+ apiKeyFormat?: string;
33
+ /**
34
+ * Workstream D — the sanitised platform-signal headers (lowercased names →
35
+ * values), e.g. `openai-organization`, `x-goog-user-project`. Matched
36
+ * against `headerPatterns` (which test on `name` and `name: value`).
37
+ */
38
+ platformHeaders?: Record<string, string>;
39
+ }
40
+ /**
41
+ * A signature definition, generic over the vendor key. The seed constant
42
+ * below uses the closed `PlatformAgentVendor` union; DB-promoted signatures
43
+ * (backend dynamic registry, console round) carry arbitrary string vendors —
44
+ * `matchPlatformSignature` accepts both.
45
+ */
46
+ interface PlatformSignatureDef<V extends string = string> {
47
+ vendor: V;
48
+ displayName: string;
49
+ uaPatterns?: RegExp[];
50
+ agentCardPatterns?: RegExp[];
51
+ /**
52
+ * Workstream D — patterns tested against an observed API-key format prefix
53
+ * (e.g. `/^sk-ant/i` → claude). A credential-header format is a stronger
54
+ * platform signal than a spoofable User-Agent.
55
+ */
56
+ apiKeyFormatPatterns?: RegExp[];
57
+ /**
58
+ * Workstream D — patterns tested against each observed platform-signal
59
+ * header, as both `name` and `name: value` (so `/openai-organization/i` and
60
+ * `/x-goog-user-project/i` match on presence).
61
+ */
62
+ headerPatterns?: RegExp[];
63
+ }
64
+ /**
65
+ * A fingerprint whose vendor may be a dynamically-promoted (non-seed) key.
66
+ * Structurally a superset of `PlatformFingerprint`.
67
+ */
68
+ interface DynamicPlatformFingerprint {
69
+ vendor: string;
70
+ fingerprintKey: string;
71
+ displayName: string;
27
72
  }
28
73
  /**
29
74
  * Registry of known platform-agent signatures. Order matters — first match wins.
@@ -32,16 +77,21 @@ interface PlatformDetectionInput {
32
77
  * platform UA strings drift between versions. False positives are tolerable —
33
78
  * the orphan record is informational, not enforcement.
34
79
  */
35
- declare const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<{
36
- vendor: PlatformAgentVendor;
37
- displayName: string;
38
- uaPatterns?: RegExp[];
39
- agentCardPatterns?: RegExp[];
40
- }>;
80
+ declare const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<PlatformSignatureDef<PlatformAgentVendor>>;
81
+ /**
82
+ * Generic first-match-wins matcher over an arbitrary signature set. The
83
+ * backend's dynamic registry merges the seed constant with DB-promoted
84
+ * signatures and calls this — ONE matching implementation everywhere
85
+ * (fingerprint-key rule included: vendor + lowercased UA basename before
86
+ * the first space/slash, falling back to the agent-card host).
87
+ */
88
+ declare function matchPlatformSignature(signatures: ReadonlyArray<PlatformSignatureDef>, input: PlatformDetectionInput): DynamicPlatformFingerprint | undefined;
41
89
  /**
42
- * Inspect caller metadata for a known platform-agent fingerprint.
43
- * Returns undefined if no signature matches.
90
+ * Inspect caller metadata for a known SEED platform-agent fingerprint.
91
+ * Returns undefined if no signature matches. (Backends with the dynamic
92
+ * registry should call `matchPlatformSignature` over their merged set
93
+ * instead — same semantics, live signature additions included.)
44
94
  */
45
95
  declare function detectPlatformFingerprint(input: PlatformDetectionInput): PlatformFingerprint | undefined;
46
96
 
47
- export { PLATFORM_AGENT_SIGNATURES, type PlatformAgentVendor, type PlatformDetectionInput, type PlatformFingerprint, detectPlatformFingerprint };
97
+ export { type DynamicPlatformFingerprint, PLATFORM_AGENT_SIGNATURES, type PlatformAgentVendor, type PlatformDetectionInput, type PlatformFingerprint, type PlatformSignatureDef, detectPlatformFingerprint, matchPlatformSignature };
@@ -13,7 +13,7 @@
13
13
  * v2.9 — TLS fingerprinting / behavioural signatures are deferred
14
14
  * (recorded in adapter requirements doc).
15
15
  */
16
- type PlatformAgentVendor = 'claude' | 'chatgpt' | 'gemini' | 'cursor' | 'goose' | 'unknown';
16
+ type PlatformAgentVendor = 'claude' | 'chatgpt' | 'gemini' | 'cursor' | 'goose' | 'perplexity' | 'unknown';
17
17
  interface PlatformFingerprint {
18
18
  vendor: PlatformAgentVendor;
19
19
  /** A stable identifier used as the orphan-agent dedup key. */
@@ -24,6 +24,51 @@ interface PlatformFingerprint {
24
24
  interface PlatformDetectionInput {
25
25
  userAgent?: string;
26
26
  agentCardUrl?: string;
27
+ /**
28
+ * Workstream D — a safe API-key format prefix observed on the caller's
29
+ * credential header (e.g. `sk-ant-api03`, `sk-proj`, `aiza`). A strong
30
+ * platform signal; never the secret. Matched against `apiKeyFormatPatterns`.
31
+ */
32
+ apiKeyFormat?: string;
33
+ /**
34
+ * Workstream D — the sanitised platform-signal headers (lowercased names →
35
+ * values), e.g. `openai-organization`, `x-goog-user-project`. Matched
36
+ * against `headerPatterns` (which test on `name` and `name: value`).
37
+ */
38
+ platformHeaders?: Record<string, string>;
39
+ }
40
+ /**
41
+ * A signature definition, generic over the vendor key. The seed constant
42
+ * below uses the closed `PlatformAgentVendor` union; DB-promoted signatures
43
+ * (backend dynamic registry, console round) carry arbitrary string vendors —
44
+ * `matchPlatformSignature` accepts both.
45
+ */
46
+ interface PlatformSignatureDef<V extends string = string> {
47
+ vendor: V;
48
+ displayName: string;
49
+ uaPatterns?: RegExp[];
50
+ agentCardPatterns?: RegExp[];
51
+ /**
52
+ * Workstream D — patterns tested against an observed API-key format prefix
53
+ * (e.g. `/^sk-ant/i` → claude). A credential-header format is a stronger
54
+ * platform signal than a spoofable User-Agent.
55
+ */
56
+ apiKeyFormatPatterns?: RegExp[];
57
+ /**
58
+ * Workstream D — patterns tested against each observed platform-signal
59
+ * header, as both `name` and `name: value` (so `/openai-organization/i` and
60
+ * `/x-goog-user-project/i` match on presence).
61
+ */
62
+ headerPatterns?: RegExp[];
63
+ }
64
+ /**
65
+ * A fingerprint whose vendor may be a dynamically-promoted (non-seed) key.
66
+ * Structurally a superset of `PlatformFingerprint`.
67
+ */
68
+ interface DynamicPlatformFingerprint {
69
+ vendor: string;
70
+ fingerprintKey: string;
71
+ displayName: string;
27
72
  }
28
73
  /**
29
74
  * Registry of known platform-agent signatures. Order matters — first match wins.
@@ -32,16 +77,21 @@ interface PlatformDetectionInput {
32
77
  * platform UA strings drift between versions. False positives are tolerable —
33
78
  * the orphan record is informational, not enforcement.
34
79
  */
35
- declare const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<{
36
- vendor: PlatformAgentVendor;
37
- displayName: string;
38
- uaPatterns?: RegExp[];
39
- agentCardPatterns?: RegExp[];
40
- }>;
80
+ declare const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<PlatformSignatureDef<PlatformAgentVendor>>;
81
+ /**
82
+ * Generic first-match-wins matcher over an arbitrary signature set. The
83
+ * backend's dynamic registry merges the seed constant with DB-promoted
84
+ * signatures and calls this — ONE matching implementation everywhere
85
+ * (fingerprint-key rule included: vendor + lowercased UA basename before
86
+ * the first space/slash, falling back to the agent-card host).
87
+ */
88
+ declare function matchPlatformSignature(signatures: ReadonlyArray<PlatformSignatureDef>, input: PlatformDetectionInput): DynamicPlatformFingerprint | undefined;
41
89
  /**
42
- * Inspect caller metadata for a known platform-agent fingerprint.
43
- * Returns undefined if no signature matches.
90
+ * Inspect caller metadata for a known SEED platform-agent fingerprint.
91
+ * Returns undefined if no signature matches. (Backends with the dynamic
92
+ * registry should call `matchPlatformSignature` over their merged set
93
+ * instead — same semantics, live signature additions included.)
44
94
  */
45
95
  declare function detectPlatformFingerprint(input: PlatformDetectionInput): PlatformFingerprint | undefined;
46
96
 
47
- export { PLATFORM_AGENT_SIGNATURES, type PlatformAgentVendor, type PlatformDetectionInput, type PlatformFingerprint, detectPlatformFingerprint };
97
+ export { type DynamicPlatformFingerprint, PLATFORM_AGENT_SIGNATURES, type PlatformAgentVendor, type PlatformDetectionInput, type PlatformFingerprint, type PlatformSignatureDef, detectPlatformFingerprint, matchPlatformSignature };
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var platform_signatures_exports = {};
22
22
  __export(platform_signatures_exports, {
23
23
  PLATFORM_AGENT_SIGNATURES: () => PLATFORM_AGENT_SIGNATURES,
24
- detectPlatformFingerprint: () => detectPlatformFingerprint
24
+ detectPlatformFingerprint: () => detectPlatformFingerprint,
25
+ matchPlatformSignature: () => matchPlatformSignature
25
26
  });
26
27
  module.exports = __toCommonJS(platform_signatures_exports);
27
28
  var PLATFORM_AGENT_SIGNATURES = [
@@ -29,19 +30,25 @@ var PLATFORM_AGENT_SIGNATURES = [
29
30
  vendor: "claude",
30
31
  displayName: "Anthropic Claude",
31
32
  uaPatterns: [/claude/i, /anthropic/i],
32
- agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i]
33
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
34
+ apiKeyFormatPatterns: [/^sk-ant/i],
35
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
33
36
  },
34
37
  {
35
38
  vendor: "chatgpt",
36
39
  displayName: "OpenAI ChatGPT",
37
40
  uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
38
- agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i]
41
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
42
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
43
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
39
44
  },
40
45
  {
41
46
  vendor: "gemini",
42
47
  displayName: "Google Gemini",
43
48
  uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
44
- agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i]
49
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
50
+ apiKeyFormatPatterns: [/^aiza/i],
51
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
45
52
  },
46
53
  {
47
54
  vendor: "cursor",
@@ -54,15 +61,31 @@ var PLATFORM_AGENT_SIGNATURES = [
54
61
  displayName: "Block Goose",
55
62
  uaPatterns: [/goose/i, /block-goose/i],
56
63
  agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
64
+ },
65
+ {
66
+ vendor: "perplexity",
67
+ displayName: "Perplexity",
68
+ uaPatterns: [/perplexity/i],
69
+ agentCardPatterns: [/perplexity\.ai/i],
70
+ apiKeyFormatPatterns: [/^pplx-/i]
57
71
  }
58
72
  ];
59
- function detectPlatformFingerprint(input) {
73
+ function matchPlatformSignature(signatures, input) {
60
74
  const ua = input.userAgent ?? "";
61
75
  const cardUrl = input.agentCardUrl ?? "";
62
- for (const sig of PLATFORM_AGENT_SIGNATURES) {
76
+ const apiKeyFormat = input.apiKeyFormat ?? "";
77
+ const headerLines = [];
78
+ if (input.platformHeaders) {
79
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
80
+ headerLines.push(name, `${name}: ${value}`);
81
+ }
82
+ }
83
+ for (const sig of signatures) {
63
84
  const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
64
85
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
65
- if (uaMatch || cardMatch) {
86
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
87
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
88
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
66
89
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
67
90
  const cardHost = (() => {
68
91
  try {
@@ -71,19 +94,23 @@ function detectPlatformFingerprint(input) {
71
94
  return "";
72
95
  }
73
96
  })();
74
- const fingerprintKey = `${sig.vendor}:${uaBase || cardHost || "unknown"}`;
97
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
75
98
  return {
76
99
  vendor: sig.vendor,
77
- fingerprintKey,
100
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
78
101
  displayName: sig.displayName
79
102
  };
80
103
  }
81
104
  }
82
105
  return void 0;
83
106
  }
107
+ function detectPlatformFingerprint(input) {
108
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
109
+ }
84
110
  // Annotate the CommonJS export names for ESM import in node:
85
111
  0 && (module.exports = {
86
112
  PLATFORM_AGENT_SIGNATURES,
87
- detectPlatformFingerprint
113
+ detectPlatformFingerprint,
114
+ matchPlatformSignature
88
115
  });
89
116
  //# sourceMappingURL=platform-signatures.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/platform-signatures.ts"],"sourcesContent":["/**\n * Platform-agent fingerprint signatures — the shared registry of known\n * platform-agent UA / agent-card patterns (Claude / ChatGPT / Gemini /\n * Cursor / Goose).\n *\n * Single source of truth, lifted verbatim from the backend's\n * `platform-agent-detector.service` (which now imports detection from\n * here): the backend's verify-access anonymous handler and the edge\n * adapter (`@astrasyncai/adapter-lambda`) must classify identically or\n * the three-tier visibility numbers drift between capture points.\n *\n * Pure regex — no Node built-ins, edge-safe. Detection is regex-only for\n * v2.9 — TLS fingerprinting / behavioural signatures are deferred\n * (recorded in adapter requirements doc).\n */\n\nexport type PlatformAgentVendor = 'claude' | 'chatgpt' | 'gemini' | 'cursor' | 'goose' | 'unknown';\n\nexport interface PlatformFingerprint {\n vendor: PlatformAgentVendor;\n /** A stable identifier used as the orphan-agent dedup key. */\n fingerprintKey: string;\n /** Human-readable name surfaced in the admin panel. */\n displayName: string;\n}\n\nexport interface PlatformDetectionInput {\n userAgent?: string;\n agentCardUrl?: string;\n}\n\n/**\n * Registry of known platform-agent signatures. Order matters — first match wins.\n *\n * Patterns are intentionally permissive (case-insensitive substring match) since\n * platform UA strings drift between versions. False positives are tolerable —\n * the orphan record is informational, not enforcement.\n */\nexport const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<{\n vendor: PlatformAgentVendor;\n displayName: string;\n uaPatterns?: RegExp[];\n agentCardPatterns?: RegExp[];\n}> = [\n {\n vendor: 'claude',\n displayName: 'Anthropic Claude',\n uaPatterns: [/claude/i, /anthropic/i],\n agentCardPatterns: [/anthropic\\.com/i, /claude\\.ai/i],\n },\n {\n vendor: 'chatgpt',\n displayName: 'OpenAI ChatGPT',\n uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\\d/i],\n agentCardPatterns: [/openai\\.com/i, /chatgpt\\.com/i],\n },\n {\n vendor: 'gemini',\n displayName: 'Google Gemini',\n uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],\n agentCardPatterns: [/gemini\\.google\\.com/i, /ai\\.google\\.dev/i],\n },\n {\n vendor: 'cursor',\n displayName: 'Cursor',\n uaPatterns: [/cursor/i],\n agentCardPatterns: [/cursor\\.sh/i, /cursor\\.com/i],\n },\n {\n vendor: 'goose',\n displayName: 'Block Goose',\n uaPatterns: [/goose/i, /block-goose/i],\n agentCardPatterns: [/block\\.xyz\\/goose/i, /goose\\.tools/i],\n },\n];\n\n/**\n * Inspect caller metadata for a known platform-agent fingerprint.\n * Returns undefined if no signature matches.\n */\nexport function detectPlatformFingerprint(\n input: PlatformDetectionInput\n): PlatformFingerprint | undefined {\n const ua = input.userAgent ?? '';\n const cardUrl = input.agentCardUrl ?? '';\n\n for (const sig of PLATFORM_AGENT_SIGNATURES) {\n const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));\n const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));\n if (uaMatch || cardMatch) {\n // Stable dedup key: vendor + lowercased UA basename (before first slash/space).\n // For requests without UA, fall back to vendor + agent-card host.\n const uaBase = ua.split(/[\\s/]/)[0]?.toLowerCase() ?? '';\n const cardHost = (() => {\n try {\n return cardUrl ? new URL(cardUrl).host : '';\n } catch {\n return '';\n }\n })();\n const fingerprintKey = `${sig.vendor}:${uaBase || cardHost || 'unknown'}`;\n return {\n vendor: sig.vendor,\n fingerprintKey,\n displayName: sig.displayName,\n };\n }\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsCO,IAAM,4BAKR;AAAA,EACH;AAAA,IACE,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,CAAC,WAAW,YAAY;AAAA,IACpC,mBAAmB,CAAC,mBAAmB,aAAa;AAAA,EACtD;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,CAAC,YAAY,WAAW,UAAU;AAAA,IAC9C,mBAAmB,CAAC,gBAAgB,eAAe;AAAA,EACrD;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,CAAC,WAAW,kBAAkB,OAAO;AAAA,IACjD,mBAAmB,CAAC,wBAAwB,kBAAkB;AAAA,EAChE;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,CAAC,SAAS;AAAA,IACtB,mBAAmB,CAAC,eAAe,cAAc;AAAA,EACnD;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,CAAC,UAAU,cAAc;AAAA,IACrC,mBAAmB,CAAC,sBAAsB,eAAe;AAAA,EAC3D;AACF;AAMO,SAAS,0BACd,OACiC;AACjC,QAAM,KAAK,MAAM,aAAa;AAC9B,QAAM,UAAU,MAAM,gBAAgB;AAEtC,aAAW,OAAO,2BAA2B;AAC3C,UAAM,UAAU,IAAI,YAAY,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;AACtD,UAAM,YAAY,IAAI,mBAAmB,KAAK,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC;AACpE,QAAI,WAAW,WAAW;AAGxB,YAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK;AACtD,YAAM,YAAY,MAAM;AACtB,YAAI;AACF,iBAAO,UAAU,IAAI,IAAI,OAAO,EAAE,OAAO;AAAA,QAC3C,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF,GAAG;AACH,YAAM,iBAAiB,GAAG,IAAI,MAAM,IAAI,UAAU,YAAY,SAAS;AACvE,aAAO;AAAA,QACL,QAAQ,IAAI;AAAA,QACZ;AAAA,QACA,aAAa,IAAI;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/platform-signatures.ts"],"sourcesContent":["/**\n * Platform-agent fingerprint signatures — the shared registry of known\n * platform-agent UA / agent-card patterns (Claude / ChatGPT / Gemini /\n * Cursor / Goose).\n *\n * Single source of truth, lifted verbatim from the backend's\n * `platform-agent-detector.service` (which now imports detection from\n * here): the backend's verify-access anonymous handler and the edge\n * adapter (`@astrasyncai/adapter-lambda`) must classify identically or\n * the three-tier visibility numbers drift between capture points.\n *\n * Pure regex — no Node built-ins, edge-safe. Detection is regex-only for\n * v2.9 — TLS fingerprinting / behavioural signatures are deferred\n * (recorded in adapter requirements doc).\n */\n\nexport type PlatformAgentVendor =\n | 'claude'\n | 'chatgpt'\n | 'gemini'\n | 'cursor'\n | 'goose'\n | 'perplexity'\n | 'unknown';\n\nexport interface PlatformFingerprint {\n vendor: PlatformAgentVendor;\n /** A stable identifier used as the orphan-agent dedup key. */\n fingerprintKey: string;\n /** Human-readable name surfaced in the admin panel. */\n displayName: string;\n}\n\nexport interface PlatformDetectionInput {\n userAgent?: string;\n agentCardUrl?: string;\n /**\n * Workstream D — a safe API-key format prefix observed on the caller's\n * credential header (e.g. `sk-ant-api03`, `sk-proj`, `aiza`). A strong\n * platform signal; never the secret. Matched against `apiKeyFormatPatterns`.\n */\n apiKeyFormat?: string;\n /**\n * Workstream D — the sanitised platform-signal headers (lowercased names →\n * values), e.g. `openai-organization`, `x-goog-user-project`. Matched\n * against `headerPatterns` (which test on `name` and `name: value`).\n */\n platformHeaders?: Record<string, string>;\n}\n\n/**\n * A signature definition, generic over the vendor key. The seed constant\n * below uses the closed `PlatformAgentVendor` union; DB-promoted signatures\n * (backend dynamic registry, console round) carry arbitrary string vendors —\n * `matchPlatformSignature` accepts both.\n */\nexport interface PlatformSignatureDef<V extends string = string> {\n vendor: V;\n displayName: string;\n uaPatterns?: RegExp[];\n agentCardPatterns?: RegExp[];\n /**\n * Workstream D — patterns tested against an observed API-key format prefix\n * (e.g. `/^sk-ant/i` → claude). A credential-header format is a stronger\n * platform signal than a spoofable User-Agent.\n */\n apiKeyFormatPatterns?: RegExp[];\n /**\n * Workstream D — patterns tested against each observed platform-signal\n * header, as both `name` and `name: value` (so `/openai-organization/i` and\n * `/x-goog-user-project/i` match on presence).\n */\n headerPatterns?: RegExp[];\n}\n\n/**\n * A fingerprint whose vendor may be a dynamically-promoted (non-seed) key.\n * Structurally a superset of `PlatformFingerprint`.\n */\nexport interface DynamicPlatformFingerprint {\n vendor: string;\n fingerprintKey: string;\n displayName: string;\n}\n\n/**\n * Registry of known platform-agent signatures. Order matters — first match wins.\n *\n * Patterns are intentionally permissive (case-insensitive substring match) since\n * platform UA strings drift between versions. False positives are tolerable —\n * the orphan record is informational, not enforcement.\n */\nexport const PLATFORM_AGENT_SIGNATURES: ReadonlyArray<PlatformSignatureDef<PlatformAgentVendor>> = [\n {\n vendor: 'claude',\n displayName: 'Anthropic Claude',\n uaPatterns: [/claude/i, /anthropic/i],\n agentCardPatterns: [/anthropic\\.com/i, /claude\\.ai/i],\n apiKeyFormatPatterns: [/^sk-ant/i],\n headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i],\n },\n {\n vendor: 'chatgpt',\n displayName: 'OpenAI ChatGPT',\n uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\\d/i],\n agentCardPatterns: [/openai\\.com/i, /chatgpt\\.com/i],\n apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],\n headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i],\n },\n {\n vendor: 'gemini',\n displayName: 'Google Gemini',\n uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],\n agentCardPatterns: [/gemini\\.google\\.com/i, /ai\\.google\\.dev/i],\n apiKeyFormatPatterns: [/^aiza/i],\n headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i],\n },\n {\n vendor: 'cursor',\n displayName: 'Cursor',\n uaPatterns: [/cursor/i],\n agentCardPatterns: [/cursor\\.sh/i, /cursor\\.com/i],\n },\n {\n vendor: 'goose',\n displayName: 'Block Goose',\n uaPatterns: [/goose/i, /block-goose/i],\n agentCardPatterns: [/block\\.xyz\\/goose/i, /goose\\.tools/i],\n },\n {\n vendor: 'perplexity',\n displayName: 'Perplexity',\n uaPatterns: [/perplexity/i],\n agentCardPatterns: [/perplexity\\.ai/i],\n apiKeyFormatPatterns: [/^pplx-/i],\n },\n];\n\n/**\n * Generic first-match-wins matcher over an arbitrary signature set. The\n * backend's dynamic registry merges the seed constant with DB-promoted\n * signatures and calls this — ONE matching implementation everywhere\n * (fingerprint-key rule included: vendor + lowercased UA basename before\n * the first space/slash, falling back to the agent-card host).\n */\nexport function matchPlatformSignature(\n signatures: ReadonlyArray<PlatformSignatureDef>,\n input: PlatformDetectionInput\n): DynamicPlatformFingerprint | undefined {\n const ua = input.userAgent ?? '';\n const cardUrl = input.agentCardUrl ?? '';\n const apiKeyFormat = input.apiKeyFormat ?? '';\n // Flatten platform headers to `name` and `name: value` lines for matching.\n const headerLines: string[] = [];\n if (input.platformHeaders) {\n for (const [name, value] of Object.entries(input.platformHeaders)) {\n headerLines.push(name, `${name}: ${value}`);\n }\n }\n\n for (const sig of signatures) {\n const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));\n const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));\n const keyMatch =\n apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));\n const headerMatch =\n headerLines.length > 0 &&\n sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));\n if (uaMatch || cardMatch || keyMatch || headerMatch) {\n const uaBase = ua.split(/[\\s/]/)[0]?.toLowerCase() ?? '';\n const cardHost = (() => {\n try {\n return cardUrl ? new URL(cardUrl).host : '';\n } catch {\n return '';\n }\n })();\n // Prefer a UA/card basename for the dedup key; fall back to the credential\n // format (still a stable per-vendor key) when only a key/header matched.\n const keyBasis = uaBase || cardHost || apiKeyFormat || 'unknown';\n return {\n vendor: sig.vendor,\n fingerprintKey: `${sig.vendor}:${keyBasis}`,\n displayName: sig.displayName,\n };\n }\n }\n\n return undefined;\n}\n\n/**\n * Inspect caller metadata for a known SEED platform-agent fingerprint.\n * Returns undefined if no signature matches. (Backends with the dynamic\n * registry should call `matchPlatformSignature` over their merged set\n * instead — same semantics, live signature additions included.)\n */\nexport function detectPlatformFingerprint(\n input: PlatformDetectionInput\n): PlatformFingerprint | undefined {\n // Seed vendors are all members of the PlatformAgentVendor union, so the\n // narrowing cast is sound by construction.\n return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input) as\n | PlatformFingerprint\n | undefined;\n}\n"],"mappings":"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","names":[]}
@@ -4,19 +4,25 @@ var PLATFORM_AGENT_SIGNATURES = [
4
4
  vendor: "claude",
5
5
  displayName: "Anthropic Claude",
6
6
  uaPatterns: [/claude/i, /anthropic/i],
7
- agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i]
7
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
8
+ apiKeyFormatPatterns: [/^sk-ant/i],
9
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
8
10
  },
9
11
  {
10
12
  vendor: "chatgpt",
11
13
  displayName: "OpenAI ChatGPT",
12
14
  uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
13
- agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i]
15
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
16
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
17
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
14
18
  },
15
19
  {
16
20
  vendor: "gemini",
17
21
  displayName: "Google Gemini",
18
22
  uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
19
- agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i]
23
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
24
+ apiKeyFormatPatterns: [/^aiza/i],
25
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
20
26
  },
21
27
  {
22
28
  vendor: "cursor",
@@ -29,15 +35,31 @@ var PLATFORM_AGENT_SIGNATURES = [
29
35
  displayName: "Block Goose",
30
36
  uaPatterns: [/goose/i, /block-goose/i],
31
37
  agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
38
+ },
39
+ {
40
+ vendor: "perplexity",
41
+ displayName: "Perplexity",
42
+ uaPatterns: [/perplexity/i],
43
+ agentCardPatterns: [/perplexity\.ai/i],
44
+ apiKeyFormatPatterns: [/^pplx-/i]
32
45
  }
33
46
  ];
34
- function detectPlatformFingerprint(input) {
47
+ function matchPlatformSignature(signatures, input) {
35
48
  const ua = input.userAgent ?? "";
36
49
  const cardUrl = input.agentCardUrl ?? "";
37
- for (const sig of PLATFORM_AGENT_SIGNATURES) {
50
+ const apiKeyFormat = input.apiKeyFormat ?? "";
51
+ const headerLines = [];
52
+ if (input.platformHeaders) {
53
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
54
+ headerLines.push(name, `${name}: ${value}`);
55
+ }
56
+ }
57
+ for (const sig of signatures) {
38
58
  const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
39
59
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
40
- if (uaMatch || cardMatch) {
60
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
61
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
62
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
41
63
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
42
64
  const cardHost = (() => {
43
65
  try {
@@ -46,18 +68,22 @@ function detectPlatformFingerprint(input) {
46
68
  return "";
47
69
  }
48
70
  })();
49
- const fingerprintKey = `${sig.vendor}:${uaBase || cardHost || "unknown"}`;
71
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
50
72
  return {
51
73
  vendor: sig.vendor,
52
- fingerprintKey,
74
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
53
75
  displayName: sig.displayName
54
76
  };
55
77
  }
56
78
  }
57
79
  return void 0;
58
80
  }
81
+ function detectPlatformFingerprint(input) {
82
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
83
+ }
59
84
  export {
60
85
  PLATFORM_AGENT_SIGNATURES,
61
- detectPlatformFingerprint
86
+ detectPlatformFingerprint,
87
+ matchPlatformSignature
62
88
  };
63
89
  //# sourceMappingURL=platform-signatures.mjs.map