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.
- package/README.md +26 -26
- package/dist/adt/config.d.ts +1 -1
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/http.d.ts +1 -1
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js.map +1 -1
- package/dist/authz/policy.d.ts +6 -0
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +20 -0
- package/dist/authz/policy.js.map +1 -1
- package/dist/cli.js +21 -3
- package/dist/cli.js.map +1 -1
- package/dist/handlers/dispatch.d.ts +3 -0
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +71 -53
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/schemas.d.ts +4 -4
- package/dist/plugins/manifest-interpreter.d.ts +25 -0
- package/dist/plugins/manifest-interpreter.d.ts.map +1 -0
- package/dist/plugins/manifest-interpreter.js +124 -0
- package/dist/plugins/manifest-interpreter.js.map +1 -0
- package/dist/public/define-tool.d.ts +9 -0
- package/dist/public/define-tool.d.ts.map +1 -0
- package/dist/public/define-tool.js +25 -0
- package/dist/public/define-tool.js.map +1 -0
- package/dist/public/index.d.ts +9 -0
- package/dist/public/index.d.ts.map +1 -0
- package/dist/public/index.js +10 -0
- package/dist/public/index.js.map +1 -0
- package/dist/public/testing.d.ts +26 -0
- package/dist/public/testing.d.ts.map +1 -0
- package/dist/public/testing.js +39 -0
- package/dist/public/testing.js.map +1 -0
- package/dist/public/types.d.ts +87 -0
- package/dist/public/types.d.ts.map +1 -0
- package/dist/public/types.js +4 -0
- package/dist/public/types.js.map +1 -0
- package/dist/registry/tool-registry.d.ts +74 -0
- package/dist/registry/tool-registry.d.ts.map +1 -0
- package/dist/registry/tool-registry.js +59 -0
- package/dist/registry/tool-registry.js.map +1 -0
- package/dist/server/app-url.d.ts +31 -0
- package/dist/server/app-url.d.ts.map +1 -0
- package/dist/server/app-url.js +50 -0
- package/dist/server/app-url.js.map +1 -0
- package/dist/server/audit.d.ts +4 -0
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +19 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/http.d.ts +15 -46
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +105 -375
- package/dist/server/http.js.map +1 -1
- package/dist/server/logger.d.ts +22 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +22 -0
- package/dist/server/logger.js.map +1 -1
- package/dist/server/plugin-loader.d.ts +19 -0
- package/dist/server/plugin-loader.d.ts.map +1 -0
- package/dist/server/plugin-loader.js +162 -0
- package/dist/server/plugin-loader.js.map +1 -0
- package/dist/server/safe-http-client.d.ts +38 -0
- package/dist/server/safe-http-client.d.ts.map +1 -0
- package/dist/server/safe-http-client.js +129 -0
- package/dist/server/safe-http-client.js.map +1 -0
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +36 -7
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -1
- package/package.json +24 -8
- package/dist/adt/btp.d.ts +0 -140
- package/dist/adt/btp.d.ts.map +0 -1
- package/dist/adt/btp.js +0 -427
- package/dist/adt/btp.js.map +0 -1
- package/dist/server/oauth-state.d.ts +0 -92
- package/dist/server/oauth-state.d.ts.map +0 -1
- package/dist/server/oauth-state.js +0 -163
- package/dist/server/oauth-state.js.map +0 -1
- package/dist/server/stateless-client-store.d.ts +0 -173
- package/dist/server/stateless-client-store.d.ts.map +0 -1
- package/dist/server/stateless-client-store.js +0 -503
- package/dist/server/stateless-client-store.js.map +0 -1
- package/dist/server/xsuaa.d.ts +0 -188
- package/dist/server/xsuaa.d.ts.map +0 -1
- package/dist/server/xsuaa.js +0 -464
- 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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"}
|