arc-1 0.9.18 → 0.9.19

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 (92) hide show
  1. package/README.md +26 -26
  2. package/dist/adt/config.d.ts +1 -1
  3. package/dist/adt/config.d.ts.map +1 -1
  4. package/dist/adt/http.d.ts +1 -1
  5. package/dist/adt/http.d.ts.map +1 -1
  6. package/dist/adt/http.js.map +1 -1
  7. package/dist/authz/policy.d.ts +6 -0
  8. package/dist/authz/policy.d.ts.map +1 -1
  9. package/dist/authz/policy.js +20 -0
  10. package/dist/authz/policy.js.map +1 -1
  11. package/dist/cli.js +21 -3
  12. package/dist/cli.js.map +1 -1
  13. package/dist/handlers/dispatch.d.ts +3 -0
  14. package/dist/handlers/dispatch.d.ts.map +1 -1
  15. package/dist/handlers/dispatch.js +71 -53
  16. package/dist/handlers/dispatch.js.map +1 -1
  17. package/dist/handlers/schemas.d.ts +4 -4
  18. package/dist/plugins/manifest-interpreter.d.ts +25 -0
  19. package/dist/plugins/manifest-interpreter.d.ts.map +1 -0
  20. package/dist/plugins/manifest-interpreter.js +124 -0
  21. package/dist/plugins/manifest-interpreter.js.map +1 -0
  22. package/dist/public/define-tool.d.ts +9 -0
  23. package/dist/public/define-tool.d.ts.map +1 -0
  24. package/dist/public/define-tool.js +25 -0
  25. package/dist/public/define-tool.js.map +1 -0
  26. package/dist/public/index.d.ts +9 -0
  27. package/dist/public/index.d.ts.map +1 -0
  28. package/dist/public/index.js +10 -0
  29. package/dist/public/index.js.map +1 -0
  30. package/dist/public/testing.d.ts +26 -0
  31. package/dist/public/testing.d.ts.map +1 -0
  32. package/dist/public/testing.js +39 -0
  33. package/dist/public/testing.js.map +1 -0
  34. package/dist/public/types.d.ts +87 -0
  35. package/dist/public/types.d.ts.map +1 -0
  36. package/dist/public/types.js +4 -0
  37. package/dist/public/types.js.map +1 -0
  38. package/dist/registry/tool-registry.d.ts +74 -0
  39. package/dist/registry/tool-registry.d.ts.map +1 -0
  40. package/dist/registry/tool-registry.js +59 -0
  41. package/dist/registry/tool-registry.js.map +1 -0
  42. package/dist/server/app-url.d.ts +31 -0
  43. package/dist/server/app-url.d.ts.map +1 -0
  44. package/dist/server/app-url.js +50 -0
  45. package/dist/server/app-url.js.map +1 -0
  46. package/dist/server/audit.d.ts +4 -0
  47. package/dist/server/audit.d.ts.map +1 -1
  48. package/dist/server/audit.js.map +1 -1
  49. package/dist/server/config.d.ts.map +1 -1
  50. package/dist/server/config.js +19 -0
  51. package/dist/server/config.js.map +1 -1
  52. package/dist/server/http.d.ts +15 -46
  53. package/dist/server/http.d.ts.map +1 -1
  54. package/dist/server/http.js +105 -375
  55. package/dist/server/http.js.map +1 -1
  56. package/dist/server/logger.d.ts +22 -0
  57. package/dist/server/logger.d.ts.map +1 -1
  58. package/dist/server/logger.js +22 -0
  59. package/dist/server/logger.js.map +1 -1
  60. package/dist/server/plugin-loader.d.ts +19 -0
  61. package/dist/server/plugin-loader.d.ts.map +1 -0
  62. package/dist/server/plugin-loader.js +162 -0
  63. package/dist/server/plugin-loader.js.map +1 -0
  64. package/dist/server/safe-http-client.d.ts +38 -0
  65. package/dist/server/safe-http-client.d.ts.map +1 -0
  66. package/dist/server/safe-http-client.js +129 -0
  67. package/dist/server/safe-http-client.js.map +1 -0
  68. package/dist/server/server.d.ts +2 -2
  69. package/dist/server/server.d.ts.map +1 -1
  70. package/dist/server/server.js +36 -7
  71. package/dist/server/server.js.map +1 -1
  72. package/dist/server/types.d.ts +8 -0
  73. package/dist/server/types.d.ts.map +1 -1
  74. package/dist/server/types.js +2 -0
  75. package/dist/server/types.js.map +1 -1
  76. package/package.json +24 -8
  77. package/dist/adt/btp.d.ts +0 -140
  78. package/dist/adt/btp.d.ts.map +0 -1
  79. package/dist/adt/btp.js +0 -427
  80. package/dist/adt/btp.js.map +0 -1
  81. package/dist/server/oauth-state.d.ts +0 -92
  82. package/dist/server/oauth-state.d.ts.map +0 -1
  83. package/dist/server/oauth-state.js +0 -163
  84. package/dist/server/oauth-state.js.map +0 -1
  85. package/dist/server/stateless-client-store.d.ts +0 -173
  86. package/dist/server/stateless-client-store.d.ts.map +0 -1
  87. package/dist/server/stateless-client-store.js +0 -503
  88. package/dist/server/stateless-client-store.js.map +0 -1
  89. package/dist/server/xsuaa.d.ts +0 -188
  90. package/dist/server/xsuaa.d.ts.map +0 -1
  91. package/dist/server/xsuaa.js +0 -464
  92. package/dist/server/xsuaa.js.map +0 -1
@@ -1,503 +0,0 @@
1
- /**
2
- * Stateless OAuth Dynamic Client Registration store.
3
- *
4
- * MCP clients (Claude Desktop, Cursor, Copilot CLI…) register dynamically
5
- * via RFC 7591 and cache the returned `client_id` locally. With an
6
- * in-memory or local-disk store, every CF push / restart wipes the
7
- * server-side registry — the cached `client_id` then fails with
8
- * `invalid_client` and the user has to clear their MCP client's OAuth
9
- * cache to recover.
10
- *
11
- * This store eliminates the storage problem entirely. Each `client_id`
12
- * is a self-validating token: it carries the registration payload
13
- * (redirect_uris, grant_types, …) plus an HMAC-SHA256 signature derived
14
- * from a server-held key. `getClient` re-derives the payload by
15
- * verifying the signature; no persistence is needed. Any process with
16
- * the same signing key can validate any client_id ever issued.
17
- *
18
- * Tradeoffs vs the persisted in-memory store:
19
- * + Survives `cf push`, `cf restart`, cell moves, multi-instance scale-out
20
- * + No external dependency, no service binding, no native module
21
- * - Per-client revocation is impossible (only TTL or full key rotation)
22
- * - Rotating the signing key invalidates every outstanding registration
23
- *
24
- * Default TTL is 30 days (matches typical refresh-token lifetimes). Setting
25
- * `ttlSeconds` to `0` or a negative value disables expiration — recommended
26
- * when MCP clients don't auto-re-register on `invalid_client` (Copilot CLI,
27
- * Cursor) and a finite TTL just produces periodic outages without security
28
- * gain. In that mode, forced revocation goes through full key rotation
29
- * (rotate the signing secret or bump `KDF_LABEL` from `arc1-dcr/v1` → `v2`).
30
- *
31
- * The signing key is derived (via HKDF-style HMAC) from the XSUAA
32
- * `clientsecret`, so it's already as stable as the service binding —
33
- * service rebinding rotates both at once, which is the right boundary.
34
- */
35
- import crypto from 'node:crypto';
36
- import { logger } from './logger.js';
37
- // ─── Constants ────────────────────────────────────────────────────────
38
- /** All DCR-issued client_ids start with this prefix. */
39
- const ID_PREFIX = 'arc1-';
40
- /**
41
- * Domain-separation label bound into the HMAC key derivation. Bumping the
42
- * suffix ("v1" → "v2") invalidates every previously-issued client_id without
43
- * requiring a service-binding rotation, which is a useful escape hatch.
44
- */
45
- const KDF_LABEL = 'arc1-dcr/v1';
46
- /** Schema version of the JSON payload embedded in the signed client_id. */
47
- const PAYLOAD_VERSION = 1;
48
- /**
49
- * Truncated HMAC-SHA256 length in bytes. 16 bytes = 128 bits, which is well
50
- * above the practical forgery threshold for opaque IDs (NIST SP 800-107
51
- * acceptable for non-replayable identifiers).
52
- */
53
- const SIG_BYTES = 16;
54
- /**
55
- * Default lifetime of a DCR registration. 30 days matches typical OAuth
56
- * refresh-token lifetimes and provides a conservative compromise window.
57
- * Set `ttlSeconds` to `0` (or any non-positive value) to disable expiration
58
- * — recommended for environments where MCP clients don't auto-re-register
59
- * on `invalid_client` (Copilot CLI, Cursor) and a finite TTL produces
60
- * periodic outages. Forced revocation in that case goes through full key
61
- * rotation (rotate the signing secret or bump `KDF_LABEL`).
62
- */
63
- const DEFAULT_TTL_SECONDS = 30 * 24 * 60 * 60;
64
- // Defaults applied when a registration omits these fields.
65
- const DEFAULT_GRANT_TYPES = ['authorization_code', 'refresh_token'];
66
- const DEFAULT_RESPONSE_TYPES = ['code'];
67
- const DEFAULT_TOKEN_AUTH_METHOD = 'client_secret_post';
68
- /**
69
- * Built-in redirect_uris for the pre-registered XSUAA client. These cover the
70
- * common MCP clients out of the box; additional URIs can be added at
71
- * `/authorize` time via `ensureRedirectUri()`. The list MUST also be registered
72
- * in `xs-security.json` — XSUAA is the authoritative validator for this client.
73
- */
74
- const XSUAA_DEFAULT_REDIRECT_URIS = [
75
- 'http://localhost:6274/oauth/callback', // MCP Inspector
76
- 'http://localhost:3000/oauth/callback', // Local dev
77
- 'https://claude.ai/api/mcp/auth_callback', // Claude Desktop
78
- 'cursor://anysphere.cursor-retrieval/oauth/callback', // Cursor
79
- 'vscode://vscode.microsoft-authentication/callback', // VS Code
80
- ];
81
- /**
82
- * Redirect-URI allowlist for the pre-registered XSUAA default client — a vendored
83
- * mirror of `oauth2-configuration.redirect-uris` in `xs-security.json`.
84
- *
85
- * ── Why ARC-1 must enforce this (not just XSUAA) ──
86
- * The issue-#214 callback proxy (see `oauth-state.ts`) sends XSUAA ARC-1's OWN
87
- * `/oauth/callback` as the redirect_uri and carries the client's real
88
- * redirect_uri inside the signed state. XSUAA therefore no longer validates the
89
- * client's redirect_uri — ARC-1 does. Without an allowlist, `ensureRedirectUri`
90
- * would auto-trust ANY redirect_uri supplied at `/authorize` for the shared
91
- * default client, letting an attacker steer a victim's authorization code to
92
- * their own URI (security audit 2026-06, follow-up to PR #352).
93
- *
94
- * ── Why vendored, not read from xs-security.json ──
95
- * `xs-security.json` is consumed by XSUAA at service-creation time and is NOT
96
- * shipped with the running app (excluded by `.cfignore`, the npm `files`
97
- * allowlist, and the Dockerfile), and the service binding does not expose the
98
- * patterns — so ARC-1 cannot read them at runtime. To prevent drift,
99
- * `tests/unit/server/stateless-client-store.test.ts` asserts this list stays
100
- * equal to `xs-security.json`. Keep the two in sync when adding a client.
101
- *
102
- * Glob semantics (xs-security.json): `*` matches within a single host/path
103
- * segment (never `/`), `**` matches across segments.
104
- */
105
- export const XSUAA_REDIRECT_URI_PATTERNS = [
106
- 'http://localhost:*/**',
107
- 'https://*.hana.ondemand.com/**',
108
- 'https://*.applicationstudio.cloud.sap/**',
109
- 'https://claude.ai/api/mcp/auth_callback',
110
- 'https://callback.mistral.ai/v1/integrations_auth/oauth2_callback',
111
- 'cursor://anysphere.cursor-retrieval/**',
112
- 'cursor://anysphere.cursor-mcp/**',
113
- 'vscode://vscode.microsoft-authentication/**',
114
- 'https://global.consent.azure-apim.net/redirect/**',
115
- ];
116
- /** Translate one xs-security.json redirect-uri glob into an anchored,
117
- * case-insensitive RegExp. `**` → `.*` (crosses `/`); `*` → `[^/]*` (within a
118
- * segment); every other character is matched literally. The trailing `/` and
119
- * anchoring mean a host-label `*` (e.g. `*.hana.ondemand.com`) cannot be widened
120
- * to a different registrable domain. */
121
- function redirectPatternToRegExp(pattern) {
122
- // Split on the wildcard tokens, keeping them (the capturing group keeps the
123
- // delimiters in the result array). `**` is tried before `*`, so it tokenizes
124
- // as a single token.
125
- const body = pattern
126
- .split(/(\*\*|\*)/)
127
- .map((segment) => {
128
- if (segment === '**')
129
- return '.*'; // crosses path separators
130
- if (segment === '*')
131
- return '[^/]*'; // within a single segment (never `/`)
132
- return segment.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); // escape literal regex metachars
133
- })
134
- .join('');
135
- return new RegExp(`^${body}$`, 'i');
136
- }
137
- const XSUAA_REDIRECT_URI_REGEXPS = XSUAA_REDIRECT_URI_PATTERNS.map(redirectPatternToRegExp);
138
- /**
139
- * Is `uri` an allowed redirect target for the pre-registered XSUAA default
140
- * client? True iff it matches an `XSUAA_REDIRECT_URI_PATTERNS` entry. Stateless,
141
- * so it gives the same answer on every instance — used both to gate dynamic
142
- * registration (`ensureRedirectUri`) and to validate the redirect target at
143
- * `/oauth/callback` (`checkRedirectUri`).
144
- *
145
- * SECURITY — parse before matching: the value matched here is later re-parsed
146
- * with `new URL()` and used as the 302 target that carries the OAuth `code`, so
147
- * the glob decision MUST agree with how the URL actually parses. The patterns
148
- * are string globs; a `*` sitting in the PORT position (the localhost pattern)
149
- * would otherwise let a URL-userinfo segment ride inside the same-segment
150
- * wildcard — `http://localhost:x@evil.com/cb` matches the `localhost:[^slash]`
151
- * port glob yet `new URL(...).host === 'evil.com'`, steering a victim's code to an
152
- * attacker host. So we reject anything that doesn't parse, reject any userinfo
153
- * (`user[:pass]@`, which no legitimate redirect_uri carries), and — for http/https —
154
- * match the glob against a subject rebuilt from the PARSED components rather than the
155
- * raw string, so `\`, `#` and `?` (authority terminators the WHATWG parser folds) cannot
156
- * relocate the host past a same-segment wildcard. After these guards, a glob match
157
- * implies the parsed host is the literal host in the pattern.
158
- */
159
- export function matchesXsuaaRedirectPattern(uri) {
160
- let parsed;
161
- try {
162
- parsed = new URL(uri);
163
- }
164
- catch {
165
- return false;
166
- }
167
- if (parsed.username !== '' || parsed.password !== '')
168
- return false;
169
- // SECURITY: match the glob against a subject rebuilt from the PARSED components, not the
170
- // raw string. For http/https, `\`, `#` and `?` are authority terminators the parser folds,
171
- // so a raw value like `https://evil.com\@x.hana.ondemand.com/cb` matches a `*.hana.ondemand.com`
172
- // glob yet parses to host `evil.com` — and `evil.com` is the host the `code`-bearing 302 reaches.
173
- // Rebuilding `${protocol}//${host}${pathname}${search}` makes the regex see that same host.
174
- // Custom schemes (cursor:, vscode:) carry no authority component, so match their raw value.
175
- const subject = parsed.protocol === 'http:' || parsed.protocol === 'https:'
176
- ? `${parsed.protocol}//${parsed.host}${parsed.pathname}${parsed.search}`
177
- : uri;
178
- return XSUAA_REDIRECT_URI_REGEXPS.some((re) => re.test(subject));
179
- }
180
- // ─── Default XSUAA client ─────────────────────────────────────────────
181
- /**
182
- * Pre-registered XSUAA client config. MCP clients that hit the XSUAA
183
- * `clientid` directly (Manual mode in Copilot Studio, etc.) resolve through
184
- * this entry instead of going through DCR.
185
- */
186
- function buildXsuaaDefaultClient(clientId, clientSecret) {
187
- return {
188
- client_id: clientId,
189
- client_secret: clientSecret,
190
- redirect_uris: [...XSUAA_DEFAULT_REDIRECT_URIS],
191
- grant_types: [...DEFAULT_GRANT_TYPES],
192
- response_types: [...DEFAULT_RESPONSE_TYPES],
193
- token_endpoint_auth_method: DEFAULT_TOKEN_AUTH_METHOD,
194
- client_name: 'ARC-1 XSUAA Default Client',
195
- };
196
- }
197
- // ─── Store ────────────────────────────────────────────────────────────
198
- export class StatelessDcrClientStore {
199
- xsuaaClient;
200
- hmacKey;
201
- ttlSeconds;
202
- now;
203
- constructor(xsuaaClientId, xsuaaClientSecret, signingSecret, options = {}) {
204
- if (!signingSecret) {
205
- throw new Error('StatelessDcrClientStore requires a non-empty signingSecret');
206
- }
207
- // Defense-in-depth: warn (don't throw) on weak signing secrets. NIST
208
- // SP 800-131A r2 sets 112 bits / 14 bytes as the HMAC floor; 128 bits /
209
- // 16 bytes is the conservative consensus across production OAuth servers
210
- // (Keycloak documents 14 chars, Okta requires 32 for client_secret_jwt,
211
- // Hydra accepts 6 silently). ARC-1's legacy default (XSUAA `clientsecret`,
212
- // typically 40+ chars) clears the bar; the realistic trigger here is a
213
- // test/dev secret. Use byte length, not char length, so multi-byte UTF-8
214
- // is measured correctly.
215
- const secretBytes = Buffer.byteLength(signingSecret, 'utf8');
216
- if (secretBytes < 16) {
217
- logger.warn('StatelessDcrClientStore signing secret is shorter than 16 bytes (128 bits) — below the recommended minimum. Use `openssl rand -base64 48` for a secure value.', { bytes: secretBytes });
218
- }
219
- // Derive a dedicated HMAC key so the raw service-binding secret is never
220
- // used directly to sign client_ids. The KDF_LABEL doubles as a domain
221
- // separator (see comment on the constant).
222
- this.hmacKey = crypto.createHmac('sha256', signingSecret).update(KDF_LABEL).digest();
223
- this.xsuaaClient = buildXsuaaDefaultClient(xsuaaClientId, xsuaaClientSecret);
224
- this.ttlSeconds = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;
225
- this.now = options.now ?? (() => Date.now());
226
- }
227
- // ── OAuthRegisteredClientsStore implementation ──
228
- async getClient(clientId) {
229
- if (clientId === this.xsuaaClient.client_id) {
230
- return this.xsuaaClient;
231
- }
232
- if (!clientId.startsWith(ID_PREFIX)) {
233
- this.emitLookupFailed(clientId, 'unknown_prefix');
234
- return undefined;
235
- }
236
- const decoded = this.decodeAndVerify(clientId);
237
- if (decoded.kind === 'error') {
238
- this.emitLookupFailed(clientId, decoded.reason);
239
- return undefined;
240
- }
241
- if (this.ttlSeconds > 0) {
242
- const ageSec = Math.floor(this.now() / 1000) - decoded.payload.iat;
243
- if (ageSec > this.ttlSeconds) {
244
- this.emitLookupFailed(clientId, 'expired');
245
- logger.debug('OAuth client expired (TTL)', { clientId, ageSec, ttlSeconds: this.ttlSeconds });
246
- return undefined;
247
- }
248
- }
249
- return this.payloadToClientInfo(clientId, decoded.payload);
250
- }
251
- async registerClient(client) {
252
- if (client.redirect_uris) {
253
- for (const uri of client.redirect_uris) {
254
- validateRedirectUri(uri);
255
- }
256
- }
257
- const issuedAt = Math.floor(this.now() / 1000);
258
- const payload = {
259
- v: PAYLOAD_VERSION,
260
- iat: issuedAt,
261
- ru: client.redirect_uris ?? [],
262
- };
263
- if (client.grant_types)
264
- payload.gt = client.grant_types;
265
- if (client.response_types)
266
- payload.rt = client.response_types;
267
- if (client.token_endpoint_auth_method)
268
- payload.am = client.token_endpoint_auth_method;
269
- if (client.client_name)
270
- payload.cn = client.client_name;
271
- const clientId = this.encode(payload);
272
- const clientSecret = this.deriveSecret(clientId);
273
- logger.debug('OAuth client registered (stateless)', {
274
- clientId,
275
- clientName: client.client_name,
276
- idBytes: clientId.length,
277
- });
278
- logger.emitAudit({
279
- timestamp: new Date().toISOString(),
280
- level: 'info',
281
- event: 'oauth_client_registered',
282
- registeredClientId: clientId,
283
- clientName: client.client_name,
284
- redirectUriCount: payload.ru.length,
285
- idBytes: clientId.length,
286
- });
287
- // RFC 7591 §3.2.1: `client_secret_expires_at` is REQUIRED when a
288
- // `client_secret` is issued. Value is the absolute expiry time in
289
- // seconds since epoch, OR exactly 0 if the secret never expires —
290
- // exactly the semantic ARC1_OAUTH_DCR_TTL_SECONDS=0 introduces.
291
- const clientSecretExpiresAt = this.ttlSeconds > 0 ? issuedAt + this.ttlSeconds : 0;
292
- return {
293
- ...client,
294
- client_id: clientId,
295
- client_secret: clientSecret,
296
- client_id_issued_at: issuedAt,
297
- client_secret_expires_at: clientSecretExpiresAt,
298
- };
299
- }
300
- // ── SDK redirect_uri hook ──
301
- /**
302
- * Called by the MCP SDK before redirect_uri validation on `/authorize`.
303
- *
304
- * For the pre-registered XSUAA client we mutate the in-memory list so the
305
- * SDK's exact-match check passes. The mutation is replayed on every
306
- * `/authorize`, so it doesn't need to persist. SECURITY: we register a
307
- * candidate URI ONLY if it matches `XSUAA_REDIRECT_URI_PATTERNS` (the vendored
308
- * mirror of xs-security.json). The issue-#214 callback proxy removed XSUAA
309
- * from the client-redirect path, so an un-gated add here would let an attacker
310
- * register an arbitrary redirect_uri and have the SDK accept it — the entry
311
- * point for authorization-code interception (security audit 2026-06). A
312
- * non-matching URI is dropped (audited); the SDK's exact-match check then
313
- * rejects the `/authorize` request before any state is minted.
314
- *
315
- * For DCR (`arc1-…`) clients we are stateless by design: there's nothing
316
- * to mutate. The previous in-memory store implemented a percent-encoding
317
- * loose-match (BAS/Theia registers `?x=1` then authorizes with `%3Fx=1`).
318
- * Reproducing that statelessly would require either bundling every
319
- * encoding variant in the signed payload or keeping a per-process scratch
320
- * map, both of which undermine the "no state" goal. We accept the
321
- * regression: affected clients re-register on encoding-variant mismatch,
322
- * which is exactly what they did under the old store after every restart.
323
- */
324
- ensureRedirectUri(clientId, uri) {
325
- if (clientId !== this.xsuaaClient.client_id)
326
- return;
327
- if (this.xsuaaClient.redirect_uris.includes(uri))
328
- return;
329
- if (!matchesXsuaaRedirectPattern(uri)) {
330
- logger.warn('Dynamic redirect_uri rejected for XSUAA default client (not in allowlist)', { clientId, uri });
331
- logger.emitAudit({
332
- timestamp: new Date().toISOString(),
333
- level: 'warn',
334
- event: 'oauth_redirect_uri_rejected',
335
- registeredClientId: clientId,
336
- redirectUri: uri,
337
- });
338
- return;
339
- }
340
- this.xsuaaClient.redirect_uris.push(uri);
341
- logger.debug('Dynamic redirect_uri registered for XSUAA client', { clientId, uri });
342
- logger.emitAudit({
343
- timestamp: new Date().toISOString(),
344
- level: 'info',
345
- event: 'oauth_redirect_uri_registered',
346
- registeredClientId: clientId,
347
- redirectUri: uri,
348
- });
349
- }
350
- /**
351
- * Validate that `uri` is an allowed redirect target for `clientId` at the
352
- * `/oauth/callback` proxy — the control that stops authorization-code
353
- * interception (security audit 2026-06, follow-up to PR #352).
354
- *
355
- * - Default (pre-registered XSUAA) client → must match the redirect-uri
356
- * allowlist (`matchesXsuaaRedirectPattern`). Deliberately consults the
357
- * static allowlist, NOT the mutable in-memory list, so the verdict is
358
- * stateless and identical on every instance — a code is never forwarded to
359
- * an unlisted URI even if `/authorize` ran on a different instance.
360
- * - DCR (`arc1-…`) client → must be one of the redirect_uris baked immutably
361
- * into the signed client_id (re-derived by `getClient`). Returns
362
- * `unknown_client` when the id is unrecognised / expired / forged.
363
- */
364
- async checkRedirectUri(clientId, uri) {
365
- if (clientId === this.xsuaaClient.client_id) {
366
- return matchesXsuaaRedirectPattern(uri) ? 'ok' : 'unregistered';
367
- }
368
- const info = await this.getClient(clientId);
369
- if (!info)
370
- return 'unknown_client';
371
- return info.redirect_uris.includes(uri) ? 'ok' : 'unregistered';
372
- }
373
- // ── Internals: encode / decode / sign / verify ──
374
- payloadToClientInfo(clientId, payload) {
375
- return {
376
- client_id: clientId,
377
- client_secret: this.deriveSecret(clientId),
378
- client_id_issued_at: payload.iat,
379
- redirect_uris: payload.ru,
380
- grant_types: payload.gt ?? [...DEFAULT_GRANT_TYPES],
381
- response_types: payload.rt ?? [...DEFAULT_RESPONSE_TYPES],
382
- token_endpoint_auth_method: payload.am ?? DEFAULT_TOKEN_AUTH_METHOD,
383
- client_name: payload.cn,
384
- };
385
- }
386
- encode(payload) {
387
- const payloadB64 = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
388
- const sig = this.sign(payloadB64);
389
- return `${ID_PREFIX}${payloadB64}.${sig}`;
390
- }
391
- /**
392
- * Decode and verify a `client_id`. Returns either the parsed payload or a
393
- * structured failure reason — the caller emits the failure as an audit
394
- * event with the right reason code (so probing attempts are observable).
395
- */
396
- decodeAndVerify(clientId) {
397
- const stripped = clientId.slice(ID_PREFIX.length);
398
- const dot = stripped.lastIndexOf('.');
399
- if (dot < 0)
400
- return { kind: 'error', reason: 'malformed' };
401
- const payloadB64 = stripped.slice(0, dot);
402
- const sigB64 = stripped.slice(dot + 1);
403
- if (!this.verifySignature(payloadB64, sigB64)) {
404
- return { kind: 'error', reason: 'bad_signature' };
405
- }
406
- const payload = parsePayload(payloadB64);
407
- if (!payload)
408
- return { kind: 'error', reason: 'invalid_payload' };
409
- return { kind: 'ok', payload };
410
- }
411
- verifySignature(payloadB64, sigB64) {
412
- const expected = Buffer.from(this.sign(payloadB64), 'base64url');
413
- const actual = Buffer.from(sigB64, 'base64url');
414
- if (actual.length !== expected.length || actual.length !== SIG_BYTES)
415
- return false;
416
- return crypto.timingSafeEqual(actual, expected);
417
- }
418
- sign(payloadB64) {
419
- const fullDigest = crypto.createHmac('sha256', this.hmacKey).update(payloadB64).digest();
420
- // Truncate to SIG_BYTES — see the comment on the constant for rationale.
421
- return fullDigest.subarray(0, SIG_BYTES).toString('base64url');
422
- }
423
- /**
424
- * The client_secret is derived deterministically from the client_id, so
425
- * any instance with the same signing key can validate it. This is the
426
- * core reason DCR survives container restarts and scales out horizontally
427
- * with no shared state.
428
- */
429
- deriveSecret(clientId) {
430
- return crypto.createHmac('sha256', this.hmacKey).update(`secret:${clientId}`).digest('base64url');
431
- }
432
- emitLookupFailed(clientId, reason) {
433
- logger.debug('OAuth client lookup failed', { clientId, reason });
434
- logger.emitAudit({
435
- timestamp: new Date().toISOString(),
436
- // 'expired' is normal-ish (TTL eviction); the rest are probing/forgery signals.
437
- level: reason === 'expired' ? 'info' : 'warn',
438
- event: 'oauth_client_lookup_failed',
439
- registeredClientId: clientId,
440
- reason,
441
- });
442
- }
443
- }
444
- // ─── Module-level helpers ─────────────────────────────────────────────
445
- /**
446
- * Parse a base64url-encoded payload back into a typed `SignedPayload`. Returns
447
- * `undefined` on any failure (decode error, JSON parse error, schema mismatch).
448
- */
449
- function parsePayload(payloadB64) {
450
- try {
451
- const json = Buffer.from(payloadB64, 'base64url').toString('utf8');
452
- const parsed = JSON.parse(json);
453
- if (parsed.v !== PAYLOAD_VERSION)
454
- return undefined;
455
- if (typeof parsed.iat !== 'number' || !Array.isArray(parsed.ru))
456
- return undefined;
457
- return parsed;
458
- }
459
- catch {
460
- return undefined;
461
- }
462
- }
463
- /**
464
- * Validate a redirect URI against the allowed scheme/host policy.
465
- *
466
- * Allowed: `https://*`, `http://` to localhost / 127.0.0.1 / [::1], and known
467
- * MCP-client custom schemes (`claude:`, `cursor:`, `vscode:`,
468
- * `vscode-insiders:`).
469
- *
470
- * Rejected: `javascript:`, `data:`, `file:`, `ftp:`, and any `http://` to
471
- * non-loopback hosts.
472
- */
473
- export function validateRedirectUri(uri) {
474
- const ALLOWED_CUSTOM_SCHEMES = ['claude:', 'cursor:', 'vscode:', 'vscode-insiders:'];
475
- const BLOCKED_SCHEMES = ['javascript:', 'data:', 'file:', 'ftp:'];
476
- for (const scheme of BLOCKED_SCHEMES) {
477
- if (uri.toLowerCase().startsWith(scheme)) {
478
- throw new Error(`Redirect URI rejected: '${scheme}' scheme is not allowed. Use https:// or a registered custom scheme.`);
479
- }
480
- }
481
- for (const scheme of ALLOWED_CUSTOM_SCHEMES) {
482
- if (uri.toLowerCase().startsWith(scheme))
483
- return;
484
- }
485
- try {
486
- const parsed = new URL(uri);
487
- if (parsed.protocol === 'https:')
488
- return;
489
- if (parsed.protocol === 'http:') {
490
- const host = parsed.hostname.toLowerCase();
491
- if (host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1')
492
- return;
493
- throw new Error(`Redirect URI rejected: http:// is only allowed for localhost/127.0.0.1. Got: '${uri}'`);
494
- }
495
- return;
496
- }
497
- catch (err) {
498
- if (err instanceof Error && err.message.startsWith('Redirect URI rejected'))
499
- throw err;
500
- // URL parsing failed for some other reason (unknown protocol etc.) — allow.
501
- }
502
- }
503
- //# sourceMappingURL=stateless-client-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stateless-client-store.js","sourceRoot":"","sources":["../../src/server/stateless-client-store.ts"],"names":[],"mappings":"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"}