@frontmcp/auth 1.8.2 → 1.8.4

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 (50) hide show
  1. package/authorities/authorities.engine.d.ts +25 -2
  2. package/authorities/authorities.engine.d.ts.map +1 -1
  3. package/authorities/authorities.validation.d.ts +27 -0
  4. package/authorities/authorities.validation.d.ts.map +1 -0
  5. package/authorities/index.d.ts +1 -0
  6. package/authorities/index.d.ts.map +1 -1
  7. package/authorization/dcr-client.registry.d.ts +13 -0
  8. package/authorization/dcr-client.registry.d.ts.map +1 -1
  9. package/esm/index.mjs +530 -91
  10. package/esm/package.json +5 -5
  11. package/index.d.ts +4 -4
  12. package/index.d.ts.map +1 -1
  13. package/index.js +615 -166
  14. package/jwks/jwks.service.d.ts +4 -1
  15. package/jwks/jwks.service.d.ts.map +1 -1
  16. package/options/app-auth.schema.d.ts +4 -0
  17. package/options/app-auth.schema.d.ts.map +1 -1
  18. package/options/interfaces.d.ts +48 -7
  19. package/options/interfaces.d.ts.map +1 -1
  20. package/options/orchestrated.schema.d.ts +8 -0
  21. package/options/orchestrated.schema.d.ts.map +1 -1
  22. package/options/schema.d.ts +4 -0
  23. package/options/schema.d.ts.map +1 -1
  24. package/options/shared.schemas.d.ts +2 -0
  25. package/options/shared.schemas.d.ts.map +1 -1
  26. package/package.json +5 -5
  27. package/session/authorization.store.d.ts +19 -3
  28. package/session/authorization.store.d.ts.map +1 -1
  29. package/session/federated-auth.session.d.ts +16 -0
  30. package/session/federated-auth.session.d.ts.map +1 -1
  31. package/session/index.d.ts +4 -0
  32. package/session/index.d.ts.map +1 -1
  33. package/session/pending-login.d.ts +23 -0
  34. package/session/pending-login.d.ts.map +1 -0
  35. package/session/signin-binding.d.ts +39 -0
  36. package/session/signin-binding.d.ts.map +1 -0
  37. package/ui/base-layout.d.ts +26 -0
  38. package/ui/base-layout.d.ts.map +1 -1
  39. package/ui/index.d.ts +2 -2
  40. package/ui/index.d.ts.map +1 -1
  41. package/ui/local-login.helper.d.ts +9 -2
  42. package/ui/local-login.helper.d.ts.map +1 -1
  43. package/ui/templates.d.ts +5 -4
  44. package/ui/templates.d.ts.map +1 -1
  45. package/utils/index.d.ts +2 -0
  46. package/utils/index.d.ts.map +1 -1
  47. package/utils/redirect-uri.utils.d.ts +16 -0
  48. package/utils/redirect-uri.utils.d.ts.map +1 -0
  49. package/utils/scope-grant.utils.d.ts +21 -0
  50. package/utils/scope-grant.utils.d.ts.map +1 -0
package/index.js CHANGED
@@ -248,6 +248,7 @@ __export(index_exports, {
248
248
  AuthoritiesProfileRegistry: () => AuthoritiesProfileRegistry,
249
249
  AuthorityDeniedError: () => AuthorityDeniedError,
250
250
  AuthorizationBase: () => AuthorizationBase,
251
+ BUILT_IN_AUTH_PAGE_CSP: () => BUILT_IN_AUTH_PAGE_CSP,
251
252
  CDN: () => CDN,
252
253
  CREDENTIALS_ACCESSOR: () => CREDENTIALS_ACCESSOR,
253
254
  CimdClientIdMismatchError: () => CimdClientIdMismatchError,
@@ -262,6 +263,7 @@ __export(index_exports, {
262
263
  CredentialProviderAlreadyRegisteredError: () => CredentialProviderAlreadyRegisteredError,
263
264
  CredentialStorageError: () => CredentialStorageError,
264
265
  CredentialsAccessorImpl: () => CredentialsAccessorImpl,
266
+ DEFAULT_ALLOWED_SCOPES: () => DEFAULT_ALLOWED_SCOPES,
265
267
  DEFAULT_INCREMENTAL_TICKET_TTL_MS: () => DEFAULT_INCREMENTAL_TICKET_TTL_MS,
266
268
  DEFAULT_RESUME_TTL_MS: () => DEFAULT_RESUME_TTL_MS,
267
269
  DEFAULT_THEME: () => DEFAULT_THEME,
@@ -269,9 +271,9 @@ __export(index_exports, {
269
271
  EagerCredentialLoader: () => EagerCredentialLoader,
270
272
  ElicitationSecretRequiredError: () => ElicitationSecretRequiredError,
271
273
  EncryptedRedisVault: () => EncryptedRedisVault,
272
- EncryptedStorageError: () => import_utils24.EncryptedStorageError,
274
+ EncryptedStorageError: () => import_utils26.EncryptedStorageError,
273
275
  EncryptedStorageSecureStoreBackend: () => EncryptedStorageSecureStoreBackend,
274
- EncryptedTypedStorage: () => import_utils24.EncryptedTypedStorage,
276
+ EncryptedTypedStorage: () => import_utils26.EncryptedTypedStorage,
275
277
  EncryptionContextNotSetError: () => EncryptionContextNotSetError,
276
278
  EncryptionKeyNotConfiguredError: () => EncryptionKeyNotConfiguredError,
277
279
  FetchCredentialMiddleware: () => FetchCredentialMiddleware,
@@ -300,6 +302,9 @@ __export(index_exports, {
300
302
  RedisAuthorizationStore: () => RedisAuthorizationStore,
301
303
  RedisSessionStore: () => RedisSessionStore,
302
304
  SECURE_STORE_ACCESSOR: () => SECURE_STORE_ACCESSOR,
305
+ SIGNIN_BINDING_COOKIE_PREFIX: () => SIGNIN_BINDING_COOKIE_PREFIX,
306
+ SIGNIN_BINDING_MAX_AGE_SECONDS: () => SIGNIN_BINDING_MAX_AGE_SECONDS,
307
+ SIGNIN_BINDING_SECURE_COOKIE_PREFIX: () => SIGNIN_BINDING_SECURE_COOKIE_PREFIX,
303
308
  ScopeDeniedError: () => ScopeDeniedError,
304
309
  SecureStoreAccessorImpl: () => SecureStoreAccessorImpl,
305
310
  SessionCredentialVault: () => SessionCredentialVault,
@@ -319,7 +324,7 @@ __export(index_exports, {
319
324
  TokenVault: () => TokenVault,
320
325
  TransparentAuthorization: () => TransparentAuthorization,
321
326
  TransportIdGenerator: () => TransportIdGenerator,
322
- TypedStorage: () => import_utils23.TypedStorage,
327
+ TypedStorage: () => import_utils25.TypedStorage,
323
328
  VaultEncryption: () => VaultEncryption,
324
329
  VaultLoadError: () => VaultLoadError,
325
330
  VaultNotFoundError: () => VaultNotFoundError,
@@ -327,6 +332,7 @@ __export(index_exports, {
327
332
  abacConditionSchema: () => abacConditionSchema,
328
333
  abacOperatorSchema: () => abacOperatorSchema,
329
334
  abacPolicySchema: () => abacPolicySchema,
335
+ advertisedScopes: () => advertisedScopes,
330
336
  aiPlatformTypeSchema: () => aiPlatformTypeSchema,
331
337
  allowsPublicAccess: () => allowsPublicAccess,
332
338
  apiKeyCredentialSchema: () => apiKeyCredentialSchema,
@@ -375,6 +381,7 @@ __export(index_exports, {
375
381
  buildToolConsentPage: () => buildToolConsentPage,
376
382
  buildUnauthorizedHeader: () => buildUnauthorizedHeader,
377
383
  buildWwwAuthenticate: () => buildWwwAuthenticate,
384
+ builtInAuthPageHeaders: () => builtInAuthPageHeaders,
378
385
  centeredCardLayout: () => centeredCardLayout,
379
386
  checkSsrfProtection: () => checkSsrfProtection,
380
387
  cimdCacheConfigSchema: () => cimdCacheConfigSchema,
@@ -394,6 +401,7 @@ __export(index_exports, {
394
401
  createLayout: () => createLayout,
395
402
  createSecureStore: () => createSecureStore,
396
403
  createSessionCredentialVault: () => createSessionCredentialVault,
404
+ createSigninBinding: () => createSigninBinding,
397
405
  createTokenStorageAdapter: () => createTokenStorageAdapter,
398
406
  credentialProviderConfigSchema: () => credentialProviderConfigSchema,
399
407
  credentialSchema: () => credentialSchema,
@@ -401,9 +409,9 @@ __export(index_exports, {
401
409
  credentialTypeSchema: () => credentialTypeSchema,
402
410
  customCredentialSchema: () => customCredentialSchema,
403
411
  decodeJwtPayloadSafe: () => decodeJwtPayloadSafe,
404
- decryptAesGcm: () => import_utils34.decryptAesGcm,
412
+ decryptAesGcm: () => import_utils36.decryptAesGcm,
405
413
  decryptSessionJson: () => decryptSessionJson,
406
- decryptValue: () => import_utils34.decryptValue,
414
+ decryptValue: () => import_utils36.decryptValue,
407
415
  defaultSessionRateLimiter: () => defaultSessionRateLimiter,
408
416
  deriveAuthorizationId: () => deriveAuthorizationId,
409
417
  deriveExpectedAudience: () => deriveExpectedAudience,
@@ -411,9 +419,9 @@ __export(index_exports, {
411
419
  deriveTypedUser: () => deriveTypedUser,
412
420
  detectAuthProviders: () => detectAuthProviders,
413
421
  detectedAuthProviderSchema: () => detectedAuthProviderSchema,
414
- encryptAesGcm: () => import_utils34.encryptAesGcm,
422
+ encryptAesGcm: () => import_utils36.encryptAesGcm,
415
423
  encryptJson: () => encryptJson,
416
- encryptValue: () => import_utils34.encryptValue,
424
+ encryptValue: () => import_utils36.encryptValue,
417
425
  encryptedBlobSchema: () => encryptedBlobSchema,
418
426
  encryptedDataSchema: () => encryptedDataSchema,
419
427
  encryptedVaultEntrySchema: () => encryptedVaultEntrySchema,
@@ -426,6 +434,8 @@ __export(index_exports, {
426
434
  federatedLoginStateSchema: () => federatedLoginStateSchema,
427
435
  federatedProviderItemSchema: () => federatedProviderItemSchema,
428
436
  federatedSelectionSchema: () => federatedSelectionSchema,
437
+ findAuthoritiesProfileProblems: () => findAuthoritiesProfileProblems,
438
+ findAuthoritiesRuleProblems: () => findAuthoritiesRuleProblems,
429
439
  flatRemoteProviderFields: () => flatRemoteProviderFields,
430
440
  fromSessionRecord: () => fromSessionRecord,
431
441
  generateAuthorizationCode: () => generateAuthorizationCode,
@@ -437,15 +447,17 @@ __export(index_exports, {
437
447
  getProviderApps: () => getProviderApps,
438
448
  getProviderScopes: () => getProviderScopes,
439
449
  getTokenSignatureFingerprint: () => getTokenSignatureFingerprint,
450
+ grantScopes: () => grantScopes,
440
451
  hasOnlyLocalhostRedirectUris: () => hasOnlyLocalhostRedirectUris,
441
452
  headerApplier: () => headerApplier,
442
- hkdfSha256: () => import_utils34.hkdfSha256,
453
+ hkdfSha256: () => import_utils36.hkdfSha256,
443
454
  incrementalAuthConfigSchema: () => incrementalAuthConfigSchema,
444
455
  isAnonymousSubject: () => isAnonymousSubject,
445
456
  isCimdClientId: () => isCimdClientId,
446
457
  isFrontMcpCredentials: () => isFrontMcpCredentials,
447
458
  isJwt: () => isJwt,
448
459
  isLocalMode: () => isLocalMode,
460
+ isLoopbackRedirectUri: () => isLoopbackRedirectUri,
449
461
  isOrchestratedLocal: () => isOrchestratedLocal,
450
462
  isOrchestratedMode: () => isOrchestratedMode,
451
463
  isOrchestratedRemote: () => isOrchestratedRemote,
@@ -454,7 +466,7 @@ __export(index_exports, {
454
466
  isRedisTokenStorage: () => isRedisTokenStorage,
455
467
  isRemoteMode: () => isRemoteMode,
456
468
  isSessionComplete: () => isSessionComplete,
457
- isSignedSession: () => import_utils16.isSignedData,
469
+ isSignedSession: () => import_utils18.isSignedData,
458
470
  isSoonExpiring: () => isSoonExpiring,
459
471
  isSoonExpiringProvider: () => isSoonExpiringProvider,
460
472
  isSqliteTokenStorage: () => isSqliteTokenStorage,
@@ -475,6 +487,7 @@ __export(index_exports, {
475
487
  noopLogger: () => noopLogger,
476
488
  normalizeIssuer: () => normalizeIssuer,
477
489
  oauthCredentialSchema: () => oauthCredentialSchema,
490
+ openPendingLogin: () => openPendingLogin,
478
491
  orchestratedLocalSchema: () => orchestratedLocalSchema,
479
492
  orchestratedRemoteSchema: () => orchestratedRemoteSchema,
480
493
  parseAuthOptions: () => parseAuthOptions,
@@ -502,6 +515,7 @@ __export(index_exports, {
502
515
  resolveRequiredScopes: () => resolveRequiredScopes,
503
516
  resourceIdRefSchema: () => resourceIdRefSchema,
504
517
  safeDecrypt: () => safeDecrypt,
518
+ sealPendingLogin: () => sealPendingLogin,
505
519
  secureStoreConfigSchema: () => secureStoreConfigSchema,
506
520
  secureStoreCustomBackendSchema: () => secureStoreCustomBackendSchema,
507
521
  secureStoreEncryptionSchema: () => secureStoreEncryptionSchema,
@@ -512,6 +526,8 @@ __export(index_exports, {
512
526
  signCredentialResumeToken: () => signCredentialResumeToken,
513
527
  signIncrementalAuthTicket: () => signIncrementalAuthTicket,
514
528
  signSession: () => signSession,
529
+ signinBindingCookieName: () => signinBindingCookieName,
530
+ signinBindingMatches: () => signinBindingMatches,
515
531
  skippedAppBehaviorSchema: () => skippedAppBehaviorSchema,
516
532
  sseTransportStateSchema: () => sseTransportStateSchema,
517
533
  sshKeyCredentialSchema: () => sshKeyCredentialSchema,
@@ -544,7 +560,8 @@ __export(index_exports, {
544
560
  verifyOrParseSession: () => verifyOrParseSession,
545
561
  verifyPkce: () => verifyPkce,
546
562
  verifySession: () => verifySession,
547
- wideLayout: () => wideLayout
563
+ wideLayout: () => wideLayout,
564
+ withoutSecretFieldValues: () => withoutSecretFieldValues
548
565
  });
549
566
  module.exports = __toCommonJS(index_exports);
550
567
 
@@ -941,6 +958,12 @@ var TRANSPARENT_JWT_ALGS = [
941
958
  ];
942
959
  var MAX_JWKS_FETCH_BYTES = 1048576;
943
960
  var MAX_JWKS_FETCH_HOPS = 3;
961
+ var MISSING_EXP_ERROR = 'missing required "exp" claim';
962
+ function isMissingExpError(error) {
963
+ if (typeof error !== "object" || error === null) return false;
964
+ const { code, claim, reason } = error;
965
+ return code === "ERR_JWT_CLAIM_VALIDATION_FAILED" && claim === "exp" && reason === "missing";
966
+ }
944
967
  var WEAK_KEY_WARNING = `
945
968
  \u26A0\uFE0F SECURITY WARNING: OAuth provider is using an RSA key smaller than 2048 bits.
946
969
  This is considered insecure and should be updated.
@@ -1010,6 +1033,7 @@ var JwksService = class {
1010
1033
  kid = typeof header?.kid === "string" ? header.kid : void 0;
1011
1034
  } catch {
1012
1035
  }
1036
+ let missingExp = false;
1013
1037
  for (const p of candidates) {
1014
1038
  let jwks;
1015
1039
  try {
@@ -1021,7 +1045,9 @@ var JwksService = class {
1021
1045
  // Pin to asymmetric algorithms so a symmetric key in the provider JWKS
1022
1046
  // can't be used to accept an HMAC-signed token, and `none` is refused.
1023
1047
  algorithms: [...TRANSPARENT_JWT_ALGS],
1024
- ...issuerConstraint ? { issuer: issuerConstraint } : {}
1048
+ ...issuerConstraint ? { issuer: issuerConstraint } : {},
1049
+ // A bearer token without `exp` would be valid forever (#272).
1050
+ requiredClaims: ["exp"]
1025
1051
  });
1026
1052
  return {
1027
1053
  ok: true,
@@ -1039,11 +1065,14 @@ var JwksService = class {
1039
1065
  if (fallbackResult.ok) {
1040
1066
  return fallbackResult;
1041
1067
  }
1068
+ if (fallbackResult.error === "missing_exp") missingExp = true;
1042
1069
  }
1043
1070
  }
1071
+ if (isMissingExpError(e)) missingExp = true;
1044
1072
  this.logger.debug("[JwksService] Failed to verify token for provider: %s", p.id, e);
1045
1073
  }
1046
1074
  }
1075
+ if (missingExp) return { ok: false, error: MISSING_EXP_ERROR };
1047
1076
  return { ok: false, error: `no_provider_verified${kid ? ` (kid=${kid})` : ""}` };
1048
1077
  }
1049
1078
  /**
@@ -1124,7 +1153,10 @@ var JwksService = class {
1124
1153
  }
1125
1154
  }
1126
1155
  const nowSec = Math.floor(Date.now() / 1e3);
1127
- if (payload.exp !== void 0 && (typeof payload.exp !== "number" || !Number.isFinite(payload.exp) || payload.exp < nowSec)) {
1156
+ if (payload.exp === void 0) {
1157
+ return { ok: false, error: "missing_exp" };
1158
+ }
1159
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp) || payload.exp <= nowSec) {
1128
1160
  return { ok: false, error: "token_expired" };
1129
1161
  }
1130
1162
  if (payload.nbf !== void 0 && (typeof payload.nbf !== "number" || !Number.isFinite(payload.nbf) || payload.nbf > nowSec)) {
@@ -1173,7 +1205,10 @@ var JwksService = class {
1173
1205
  * 1) inline jwks (if provided) → cache & return
1174
1206
  * 2) cached & fresh (TTL) → return
1175
1207
  * 3) explicit jwksUri → fetch, cache, return
1176
- * 4) discover jwks_uri via AS → fetch AS metadata, then jwks_uri, cache, return
1208
+ * 4) discover jwks_uri → the issuer's OAuth authorization server
1209
+ * metadata (RFC 8414), else its OpenID configuration (OpenID Connect
1210
+ * Discovery 1.0 §4, where Google, Okta, Auth0 and Entra publish it),
1211
+ * then fetch jwks_uri, cache, return
1177
1212
  */
1178
1213
  async getJwksForProvider(ref) {
1179
1214
  if (ref.jwks?.keys?.length) {
@@ -1189,11 +1224,13 @@ var JwksService = class {
1189
1224
  if (fromUri?.keys?.length) return fromUri;
1190
1225
  }
1191
1226
  const issuer = trimSlash(ref.issuerUrl);
1192
- const meta = await this.tryFetchAsMeta(`${issuer}/.well-known/oauth-authorization-server`);
1193
- const uri = meta && typeof meta === "object" && meta["jwks_uri"] ? String(meta["jwks_uri"]) : void 0;
1194
- if (uri) {
1195
- const fromMeta = await this.tryFetchJwks(ref.id, uri);
1196
- if (fromMeta?.keys?.length) return fromMeta;
1227
+ for (const document of ["oauth-authorization-server", "openid-configuration"]) {
1228
+ const meta = await this.tryFetchAsMeta(`${issuer}/.well-known/${document}`);
1229
+ const uri = meta && typeof meta === "object" && meta["jwks_uri"] ? String(meta["jwks_uri"]) : void 0;
1230
+ if (uri) {
1231
+ const fromMeta = await this.tryFetchJwks(ref.id, uri);
1232
+ if (fromMeta?.keys?.length) return fromMeta;
1233
+ }
1197
1234
  }
1198
1235
  const maxStaleMs = Math.max(this.opts.providerJwksTtlMs * 4, 6e4);
1199
1236
  if (cached && Date.now() - cached.fetchedAt < maxStaleMs) {
@@ -1380,6 +1417,26 @@ var CDN = {
1380
1417
  stylesheet: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
1381
1418
  }
1382
1419
  };
1420
+ var BUILT_IN_AUTH_PAGE_CSP = [
1421
+ "default-src 'self'",
1422
+ "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
1423
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
1424
+ "font-src 'self' https://fonts.gstatic.com",
1425
+ "img-src 'self' data: https:",
1426
+ "connect-src 'self'",
1427
+ "object-src 'none'",
1428
+ "base-uri 'none'",
1429
+ "frame-ancestors 'none'"
1430
+ ].join("; ");
1431
+ function builtInAuthPageHeaders(options = {}) {
1432
+ return {
1433
+ ...options.csp === false ? {} : { "Content-Security-Policy": BUILT_IN_AUTH_PAGE_CSP },
1434
+ "X-Frame-Options": "DENY",
1435
+ "X-Content-Type-Options": "nosniff",
1436
+ "Referrer-Policy": "same-origin",
1437
+ "Cache-Control": "no-store"
1438
+ };
1439
+ }
1383
1440
  var DEFAULT_THEME = {
1384
1441
  colors: {
1385
1442
  primary: "#3b82f6",
@@ -1617,7 +1674,7 @@ function buildIncrementalAuthPage(params) {
1617
1674
  </div>
1618
1675
  </div>
1619
1676
 
1620
- <form method="GET" action="${escapeHtml3(callbackPath)}" class="flex gap-3 pt-4 border-t border-gray-100">
1677
+ <form method="POST" action="${escapeHtml3(callbackPath)}" class="flex gap-3 pt-4 border-t border-gray-100">
1621
1678
  <input type="hidden" name="pending_auth_id" value="${escapeHtml3(sessionHint)}">
1622
1679
  <button type="button" onclick="window.close()"
1623
1680
  class="flex-1 px-4 py-2.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 font-medium rounded-lg transition-colors">
@@ -1663,7 +1720,7 @@ function buildFederatedLoginPage(params) {
1663
1720
  ${escapeHtml3(clientName)} uses multiple authentication providers. Select which ones you want to authorize.
1664
1721
  </p>
1665
1722
 
1666
- <form method="GET" action="${escapeHtml3(callbackPath)}" id="federated-form">
1723
+ <form method="POST" action="${escapeHtml3(callbackPath)}" id="federated-form">
1667
1724
  <input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
1668
1725
  <input type="hidden" name="federated" value="true">
1669
1726
 
@@ -1808,7 +1865,7 @@ function buildToolConsentPage(params) {
1808
1865
  ${errorBanner}
1809
1866
  ${userInfo}
1810
1867
 
1811
- <form method="GET" action="${escapeHtml3(callbackPath)}" id="consent-form">
1868
+ <form method="POST" action="${escapeHtml3(callbackPath)}" id="consent-form">
1812
1869
  <input type="hidden" name="csrf" value="${escapeHtml3(csrfToken)}">
1813
1870
  <input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
1814
1871
  <input type="hidden" name="consent_submitted" value="1">
@@ -1906,7 +1963,7 @@ function buildLoginPage(params) {
1906
1963
 
1907
1964
  ${scopesHtml}
1908
1965
 
1909
- <form method="GET" action="${escapeHtml3(callbackPath)}">
1966
+ <form method="POST" action="${escapeHtml3(callbackPath)}">
1910
1967
  <input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
1911
1968
  ${fieldsHtml}
1912
1969
 
@@ -2003,7 +2060,17 @@ function toLoginExtraFields(fields, values) {
2003
2060
  value: values?.[name]
2004
2061
  }));
2005
2062
  }
2006
- function renderLocalLoginPage(login, ctx, values) {
2063
+ function withoutSecretFieldValues(values, fields) {
2064
+ if (!values) return void 0;
2065
+ const kept = {};
2066
+ for (const [name, value] of Object.entries(values)) {
2067
+ if (fields?.[name]?.type === "password") continue;
2068
+ kept[name] = value;
2069
+ }
2070
+ return kept;
2071
+ }
2072
+ function renderLocalLoginPage(login, ctx, submitted) {
2073
+ const values = withoutSecretFieldValues(submitted, login?.fields);
2007
2074
  if (login?.render) {
2008
2075
  return login.render({ ...ctx, values });
2009
2076
  }
@@ -2110,7 +2177,8 @@ function buildPendingRecord(params) {
2110
2177
  priorAuthorizedAppIds: params.priorAuthorizedAppIds,
2111
2178
  federatedLogin: params.federatedLogin,
2112
2179
  consent: params.consent,
2113
- authUiCsrf: params.authUiCsrf
2180
+ authUiCsrf: params.authUiCsrf,
2181
+ signinBinding: params.signinBinding
2114
2182
  };
2115
2183
  }
2116
2184
  function buildRefreshTokenRecord(params) {
@@ -2340,8 +2408,58 @@ var RedisAuthorizationStore = class {
2340
2408
  }
2341
2409
  };
2342
2410
 
2343
- // libs/auth/src/session/storage-authorization.store.ts
2411
+ // libs/auth/src/session/pending-login.ts
2344
2412
  var import_utils6 = require("@frontmcp/utils");
2413
+ var SALT = new TextEncoder().encode("frontmcp-pending-login");
2414
+ function keyFor(secret, pendingAuthId) {
2415
+ return (0, import_utils6.hkdfSha256)(secret, SALT, new TextEncoder().encode(`pending-login:${pendingAuthId}`), 32);
2416
+ }
2417
+ function sealPendingLogin(secret, pendingAuthId, state) {
2418
+ const iv = (0, import_utils6.randomBytes)(12);
2419
+ const { ciphertext, tag } = (0, import_utils6.encryptAesGcm)(
2420
+ keyFor(secret, pendingAuthId),
2421
+ new TextEncoder().encode(JSON.stringify(state)),
2422
+ iv
2423
+ );
2424
+ return [iv, tag, ciphertext].map((part) => (0, import_utils6.base64urlEncode)(part)).join(".");
2425
+ }
2426
+ function openPendingLogin(secret, pendingAuthId, sealed) {
2427
+ try {
2428
+ const [iv, tag, ciphertext] = sealed.split(".").map((part) => (0, import_utils6.base64urlDecode)(part));
2429
+ if (!iv || !tag || !ciphertext) return void 0;
2430
+ const plaintext = (0, import_utils6.decryptAesGcm)(keyFor(secret, pendingAuthId), ciphertext, iv, tag);
2431
+ const state = JSON.parse(new TextDecoder().decode(plaintext));
2432
+ return typeof state?.sub === "string" && state.sub.length > 0 ? state : void 0;
2433
+ } catch {
2434
+ return void 0;
2435
+ }
2436
+ }
2437
+
2438
+ // libs/auth/src/session/signin-binding.ts
2439
+ var import_utils7 = require("@frontmcp/utils");
2440
+ var SIGNIN_BINDING_COOKIE_PREFIX = "frontmcp_signin_";
2441
+ var SIGNIN_BINDING_SECURE_COOKIE_PREFIX = `__Host-${SIGNIN_BINDING_COOKIE_PREFIX}`;
2442
+ var SIGNIN_BINDING_MAX_AGE_SECONDS = 30 * 60;
2443
+ function signinBindingCookieName(pendingAuthId, options = {}) {
2444
+ const prefix = options.secure ? SIGNIN_BINDING_SECURE_COOKIE_PREFIX : SIGNIN_BINDING_COOKIE_PREFIX;
2445
+ return `${prefix}${(0, import_utils7.sha256Hex)(`signin:${pendingAuthId}`).slice(0, 16)}`;
2446
+ }
2447
+ function createSigninBinding(pendingAuthId, options = {}) {
2448
+ const value = (0, import_utils7.base64urlEncode)((0, import_utils7.randomBytes)(32));
2449
+ return { cookieName: signinBindingCookieName(pendingAuthId, options), value, hash: (0, import_utils7.sha256Hex)(value) };
2450
+ }
2451
+ function signinBindingMatches(cookieHeader, pendingAuthId, expectedHash, options = {}) {
2452
+ if (!expectedHash) return false;
2453
+ const value = (0, import_utils7.getCookie)(cookieHeader, signinBindingCookieName(pendingAuthId, options));
2454
+ if (!value) return false;
2455
+ const encoder = new TextEncoder();
2456
+ const actual = encoder.encode((0, import_utils7.sha256Hex)(value));
2457
+ const expected = encoder.encode(expectedHash);
2458
+ return actual.length === expected.length && (0, import_utils7.timingSafeEqual)(actual, expected);
2459
+ }
2460
+
2461
+ // libs/auth/src/session/storage-authorization.store.ts
2462
+ var import_utils8 = require("@frontmcp/utils");
2345
2463
  var StorageAuthorizationStore = class {
2346
2464
  codeStore;
2347
2465
  pendingStore;
@@ -2352,9 +2470,9 @@ var StorageAuthorizationStore = class {
2352
2470
  this.namespace = options.namespace ?? "oauth";
2353
2471
  this.storageIsNamespaced = this.isNamespacedStorage(storage);
2354
2472
  const base = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
2355
- this.codeStore = new import_utils6.TypedStorage(base);
2356
- this.pendingStore = new import_utils6.TypedStorage(base);
2357
- this.refreshStore = new import_utils6.TypedStorage(base);
2473
+ this.codeStore = new import_utils8.TypedStorage(base);
2474
+ this.pendingStore = new import_utils8.TypedStorage(base);
2475
+ this.refreshStore = new import_utils8.TypedStorage(base);
2358
2476
  }
2359
2477
  // ============================================
2360
2478
  // Record builders (pure; shared with in-memory store)
@@ -2727,11 +2845,11 @@ var authorizationVaultEntrySchema = import_lazy_zod2.z.object({
2727
2845
  });
2728
2846
 
2729
2847
  // libs/auth/src/session/session-credential-vault.ts
2730
- var import_utils8 = require("@frontmcp/utils");
2848
+ var import_utils10 = require("@frontmcp/utils");
2731
2849
 
2732
2850
  // libs/auth/src/session/vault-encryption.ts
2733
2851
  var import_lazy_zod3 = require("@frontmcp/lazy-zod");
2734
- var import_utils7 = require("@frontmcp/utils");
2852
+ var import_utils9 = require("@frontmcp/utils");
2735
2853
  var encryptedDataSchema = import_lazy_zod3.z.object({
2736
2854
  /** Version for future algorithm changes */
2737
2855
  v: import_lazy_zod3.z.literal(1),
@@ -2771,7 +2889,7 @@ var VaultEncryption = class {
2771
2889
  ];
2772
2890
  const ikm = new TextEncoder().encode(ikmParts.join(""));
2773
2891
  const infoBytes = new TextEncoder().encode(this.hkdfInfo);
2774
- const key = await (0, import_utils7.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
2892
+ const key = await (0, import_utils9.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
2775
2893
  return key;
2776
2894
  }
2777
2895
  /**
@@ -2798,7 +2916,7 @@ var VaultEncryption = class {
2798
2916
  ];
2799
2917
  const ikm = new TextEncoder().encode(ikmParts.join(""));
2800
2918
  const infoBytes = new TextEncoder().encode(this.hkdfInfo);
2801
- const key = await (0, import_utils7.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
2919
+ const key = await (0, import_utils9.hkdfSha256)(ikm, new Uint8Array(0), infoBytes, 32);
2802
2920
  return key;
2803
2921
  }
2804
2922
  /**
@@ -2812,14 +2930,14 @@ var VaultEncryption = class {
2812
2930
  if (key.length !== 32) {
2813
2931
  throw new Error("Encryption key must be 32 bytes");
2814
2932
  }
2815
- const iv = (0, import_utils7.randomBytes)(12);
2816
- const { ciphertext, tag } = await (0, import_utils7.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
2933
+ const iv = (0, import_utils9.randomBytes)(12);
2934
+ const { ciphertext, tag } = await (0, import_utils9.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
2817
2935
  return {
2818
2936
  v: 1,
2819
2937
  alg: "aes-256-gcm",
2820
- iv: (0, import_utils7.base64urlEncode)(iv),
2821
- ct: (0, import_utils7.base64urlEncode)(ciphertext),
2822
- tag: (0, import_utils7.base64urlEncode)(tag)
2938
+ iv: (0, import_utils9.base64urlEncode)(iv),
2939
+ ct: (0, import_utils9.base64urlEncode)(ciphertext),
2940
+ tag: (0, import_utils9.base64urlEncode)(tag)
2823
2941
  };
2824
2942
  }
2825
2943
  /**
@@ -2839,11 +2957,11 @@ var VaultEncryption = class {
2839
2957
  throw new Error("Invalid encrypted data format");
2840
2958
  }
2841
2959
  const { iv, ct, tag } = parsed.data;
2842
- const ivBuffer = (0, import_utils7.base64urlDecode)(iv);
2843
- const ciphertext = (0, import_utils7.base64urlDecode)(ct);
2844
- const tagBuffer = (0, import_utils7.base64urlDecode)(tag);
2960
+ const ivBuffer = (0, import_utils9.base64urlDecode)(iv);
2961
+ const ciphertext = (0, import_utils9.base64urlDecode)(ct);
2962
+ const tagBuffer = (0, import_utils9.base64urlDecode)(tag);
2845
2963
  try {
2846
- const plaintext = await (0, import_utils7.decryptAesGcm)(key, ciphertext, ivBuffer, tagBuffer);
2964
+ const plaintext = await (0, import_utils9.decryptAesGcm)(key, ciphertext, ivBuffer, tagBuffer);
2847
2965
  return new TextDecoder().decode(plaintext);
2848
2966
  } catch (_error) {
2849
2967
  throw new Error("Decryption failed: invalid key or corrupted data");
@@ -2983,7 +3101,7 @@ var SessionCredentialVault = class {
2983
3101
  * it expires via TTL when configured.
2984
3102
  */
2985
3103
  async rotateVault(sub) {
2986
- const vaultId = (0, import_utils8.randomUUID)();
3104
+ const vaultId = (0, import_utils10.randomUUID)();
2987
3105
  await this.storage.set(this.indexKey(sub), vaultId, this.credentialSetOpts);
2988
3106
  return vaultId;
2989
3107
  }
@@ -3132,19 +3250,19 @@ var SessionCredentialVault = class {
3132
3250
  }
3133
3251
  };
3134
3252
  function resolveVaultPepper(logger) {
3135
- const fromEnv = (0, import_utils8.getEnv)("VAULT_SECRET") ?? (0, import_utils8.getEnv)("JWT_SECRET");
3253
+ const fromEnv = (0, import_utils10.getEnv)("VAULT_SECRET") ?? (0, import_utils10.getEnv)("JWT_SECRET");
3136
3254
  if (fromEnv && fromEnv.trim().length > 0) return fromEnv;
3137
3255
  logger.warn(
3138
3256
  "SessionCredentialVault: neither VAULT_SECRET nor JWT_SECRET is set; using a random per-process pepper. Stored credentials will NOT survive a restart and cannot be shared across instances. Set VAULT_SECRET (or JWT_SECRET) for persistent, multi-instance credential storage."
3139
3257
  );
3140
- return (0, import_utils8.randomUUID)() + (0, import_utils8.randomUUID)();
3258
+ return (0, import_utils10.randomUUID)() + (0, import_utils10.randomUUID)();
3141
3259
  }
3142
3260
  function errMsg(err) {
3143
3261
  return err instanceof Error ? err.message : String(err);
3144
3262
  }
3145
3263
 
3146
3264
  // libs/auth/src/session/token-storage.factory.ts
3147
- var import_utils9 = require("@frontmcp/utils");
3265
+ var import_utils11 = require("@frontmcp/utils");
3148
3266
  function isRedisTokenStorage(config) {
3149
3267
  return typeof config === "object" && config !== null && "redis" in config && !!config.redis;
3150
3268
  }
@@ -3156,7 +3274,7 @@ function isPersistentTokenStorage(config) {
3156
3274
  }
3157
3275
  async function createTokenStorageAdapter(config) {
3158
3276
  if (config === void 0 || config === "memory") {
3159
- const adapter = new import_utils9.MemoryStorageAdapter();
3277
+ const adapter = new import_utils11.MemoryStorageAdapter();
3160
3278
  await adapter.connect();
3161
3279
  return adapter;
3162
3280
  }
@@ -3173,14 +3291,14 @@ async function createTokenStorageAdapter(config) {
3173
3291
  }
3174
3292
  if (isRedisTokenStorage(config)) {
3175
3293
  const { host, port, password, db, tls, keyPrefix } = config.redis;
3176
- const adapter = new import_utils9.RedisStorageAdapter({
3294
+ const adapter = new import_utils11.RedisStorageAdapter({
3177
3295
  config: { host, port, password, db, tls },
3178
3296
  keyPrefix: keyPrefix ?? ""
3179
3297
  });
3180
3298
  await adapter.connect();
3181
3299
  return adapter;
3182
3300
  }
3183
- const fallback = new import_utils9.MemoryStorageAdapter();
3301
+ const fallback = new import_utils11.MemoryStorageAdapter();
3184
3302
  await fallback.connect();
3185
3303
  return fallback;
3186
3304
  }
@@ -3200,7 +3318,7 @@ async function createSessionCredentialVault(options) {
3200
3318
  }
3201
3319
 
3202
3320
  // libs/auth/src/session/credential-resume-link.ts
3203
- var import_utils10 = require("@frontmcp/utils");
3321
+ var import_utils12 = require("@frontmcp/utils");
3204
3322
  var DEFAULT_RESUME_TTL_MS = 6e5;
3205
3323
  function signCredentialResumeToken(input, secret) {
3206
3324
  const payload = {
@@ -3209,7 +3327,7 @@ function signCredentialResumeToken(input, secret) {
3209
3327
  ...input.context !== void 0 ? { context: input.context } : {},
3210
3328
  exp: Date.now() + (input.ttlMs ?? DEFAULT_RESUME_TTL_MS)
3211
3329
  };
3212
- const signedJson = (0, import_utils10.signData)(payload, { secret });
3330
+ const signedJson = (0, import_utils12.signData)(payload, { secret });
3213
3331
  return Buffer.from(signedJson, "utf8").toString("base64url");
3214
3332
  }
3215
3333
  function verifyCredentialResumeToken(token, secret, now = Date.now()) {
@@ -3219,7 +3337,7 @@ function verifyCredentialResumeToken(token, secret, now = Date.now()) {
3219
3337
  } catch {
3220
3338
  return null;
3221
3339
  }
3222
- const payload = (0, import_utils10.verifyData)(signedJson, { secret });
3340
+ const payload = (0, import_utils12.verifyData)(signedJson, { secret });
3223
3341
  if (!payload) return null;
3224
3342
  if (typeof payload.sub !== "string" || typeof payload.key !== "string" || !Number.isFinite(payload.exp)) {
3225
3343
  return null;
@@ -3238,7 +3356,7 @@ function buildCredentialResumeUrl(basePath, token) {
3238
3356
  }
3239
3357
 
3240
3358
  // libs/auth/src/session/incremental-auth-ticket.ts
3241
- var import_utils11 = require("@frontmcp/utils");
3359
+ var import_utils13 = require("@frontmcp/utils");
3242
3360
  var DEFAULT_INCREMENTAL_TICKET_TTL_MS = 3e5;
3243
3361
  function signIncrementalAuthTicket(input, secret) {
3244
3362
  const payload = {
@@ -3246,10 +3364,10 @@ function signIncrementalAuthTicket(input, secret) {
3246
3364
  appId: input.appId,
3247
3365
  ...input.toolId !== void 0 ? { toolId: input.toolId } : {},
3248
3366
  ...input.priorAppIds !== void 0 ? { priorAppIds: input.priorAppIds } : {},
3249
- jti: (0, import_utils11.randomUUID)(),
3367
+ jti: (0, import_utils13.randomUUID)(),
3250
3368
  exp: Date.now() + (input.ttlMs ?? DEFAULT_INCREMENTAL_TICKET_TTL_MS)
3251
3369
  };
3252
- const signedJson = (0, import_utils11.signData)(payload, { secret });
3370
+ const signedJson = (0, import_utils13.signData)(payload, { secret });
3253
3371
  return Buffer.from(signedJson, "utf8").toString("base64url");
3254
3372
  }
3255
3373
  function verifyIncrementalAuthTicket(token, secret, now = Date.now()) {
@@ -3259,7 +3377,7 @@ function verifyIncrementalAuthTicket(token, secret, now = Date.now()) {
3259
3377
  } catch {
3260
3378
  return null;
3261
3379
  }
3262
- const payload = (0, import_utils11.verifyData)(signedJson, { secret });
3380
+ const payload = (0, import_utils13.verifyData)(signedJson, { secret });
3263
3381
  if (!payload) return null;
3264
3382
  if (typeof payload.sub !== "string" || payload.sub.length === 0) return null;
3265
3383
  if (typeof payload.appId !== "string" || payload.appId.length === 0) return null;
@@ -3439,7 +3557,7 @@ function errMsg2(err) {
3439
3557
  var SECURE_STORE_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:SECURE_STORE_ACCESSOR");
3440
3558
 
3441
3559
  // libs/auth/src/session/secure-store-accessor.impl.ts
3442
- var import_utils12 = require("@frontmcp/utils");
3560
+ var import_utils14 = require("@frontmcp/utils");
3443
3561
  var GLOBAL_NAMESPACE = "global";
3444
3562
  var SecureStoreAccessorImpl = class {
3445
3563
  backend;
@@ -3468,12 +3586,12 @@ var SecureStoreAccessorImpl = class {
3468
3586
  return GLOBAL_NAMESPACE;
3469
3587
  case "session": {
3470
3588
  const sid = this.resolveSessionId();
3471
- return sid ? `s:${(0, import_utils12.sha256Hex)(sid).slice(0, 32)}` : void 0;
3589
+ return sid ? `s:${(0, import_utils14.sha256Hex)(sid).slice(0, 32)}` : void 0;
3472
3590
  }
3473
3591
  case "user":
3474
3592
  default: {
3475
3593
  const sub = this.resolveSub();
3476
- return sub ? `u:${(0, import_utils12.sha256Hex)(sub).slice(0, 32)}` : void 0;
3594
+ return sub ? `u:${(0, import_utils14.sha256Hex)(sub).slice(0, 32)}` : void 0;
3477
3595
  }
3478
3596
  }
3479
3597
  }
@@ -3514,7 +3632,7 @@ function errMsg3(err) {
3514
3632
  }
3515
3633
 
3516
3634
  // libs/auth/src/session/secure-store.factory.ts
3517
- var import_utils13 = require("@frontmcp/utils");
3635
+ var import_utils15 = require("@frontmcp/utils");
3518
3636
  function isCustomBackendConfig(config) {
3519
3637
  return typeof config === "object" && config !== null && "backend" in config && !!config.backend;
3520
3638
  }
@@ -3563,7 +3681,7 @@ async function createSecureStore(options) {
3563
3681
  if (storage) {
3564
3682
  adapter = storage;
3565
3683
  } else if (kind === "memory") {
3566
- adapter = new import_utils13.MemoryStorageAdapter();
3684
+ adapter = new import_utils15.MemoryStorageAdapter();
3567
3685
  await adapter.connect();
3568
3686
  } else {
3569
3687
  adapter = await createTokenStorageAdapter(toTokenStorageConfig(config));
@@ -3577,7 +3695,7 @@ async function createSecureStore(options) {
3577
3695
  }
3578
3696
 
3579
3697
  // libs/auth/src/session/token.vault.ts
3580
- var import_utils14 = require("@frontmcp/utils");
3698
+ var import_utils16 = require("@frontmcp/utils");
3581
3699
  var TokenVault = class {
3582
3700
  /** Active key used for new encryptions */
3583
3701
  active;
@@ -3606,14 +3724,14 @@ var TokenVault = class {
3606
3724
  this.keys.set(k.kid, k.key);
3607
3725
  }
3608
3726
  async encrypt(plaintext, opts) {
3609
- const iv = (0, import_utils14.randomBytes)(12);
3610
- const { ciphertext, tag } = await (0, import_utils14.encryptAesGcm)(this.active.key, new TextEncoder().encode(plaintext), iv);
3727
+ const iv = (0, import_utils16.randomBytes)(12);
3728
+ const { ciphertext, tag } = await (0, import_utils16.encryptAesGcm)(this.active.key, new TextEncoder().encode(plaintext), iv);
3611
3729
  return {
3612
3730
  alg: "A256GCM",
3613
3731
  kid: this.active.kid,
3614
- iv: (0, import_utils14.base64urlEncode)(iv),
3615
- tag: (0, import_utils14.base64urlEncode)(tag),
3616
- data: (0, import_utils14.base64urlEncode)(ciphertext),
3732
+ iv: (0, import_utils16.base64urlEncode)(iv),
3733
+ tag: (0, import_utils16.base64urlEncode)(tag),
3734
+ data: (0, import_utils16.base64urlEncode)(ciphertext),
3617
3735
  exp: opts?.exp,
3618
3736
  meta: opts?.meta
3619
3737
  };
@@ -3627,16 +3745,16 @@ var TokenVault = class {
3627
3745
  }
3628
3746
  const key = this.keys.get(blob.kid);
3629
3747
  if (!key) throw new Error(`vault_unknown_kid:${blob.kid}`);
3630
- const iv = (0, import_utils14.base64urlDecode)(blob.iv);
3631
- const tag = (0, import_utils14.base64urlDecode)(blob.tag);
3632
- const data = (0, import_utils14.base64urlDecode)(blob.data);
3633
- const plaintext = await (0, import_utils14.decryptAesGcm)(key, data, iv, tag);
3748
+ const iv = (0, import_utils16.base64urlDecode)(blob.iv);
3749
+ const tag = (0, import_utils16.base64urlDecode)(blob.tag);
3750
+ const data = (0, import_utils16.base64urlDecode)(blob.data);
3751
+ const plaintext = await (0, import_utils16.decryptAesGcm)(key, data, iv, tag);
3634
3752
  return new TextDecoder().decode(plaintext);
3635
3753
  }
3636
3754
  };
3637
3755
 
3638
3756
  // libs/auth/src/session/index.ts
3639
- var import_utils34 = require("@frontmcp/utils");
3757
+ var import_utils36 = require("@frontmcp/utils");
3640
3758
 
3641
3759
  // libs/auth/src/session/transport-session.types.ts
3642
3760
  var import_lazy_zod4 = require("@frontmcp/lazy-zod");
@@ -3732,10 +3850,10 @@ var redisConfigSchema = import_lazy_zod4.z.object({
3732
3850
  });
3733
3851
 
3734
3852
  // libs/auth/src/session/session-crypto.ts
3735
- var import_utils16 = require("@frontmcp/utils");
3853
+ var import_utils18 = require("@frontmcp/utils");
3736
3854
 
3737
3855
  // libs/auth/src/errors/auth-internal.error.ts
3738
- var import_utils15 = require("@frontmcp/utils");
3856
+ var import_utils17 = require("@frontmcp/utils");
3739
3857
  var AuthInternalError = class extends Error {
3740
3858
  /**
3741
3859
  * Unique error ID for tracking in logs.
@@ -3757,7 +3875,7 @@ var AuthInternalError = class extends Error {
3757
3875
  super(message);
3758
3876
  this.name = this.constructor.name;
3759
3877
  this.code = code;
3760
- this.errorId = `err_${(0, import_utils15.bytesToHex)((0, import_utils15.randomBytes)(8))}`;
3878
+ this.errorId = `err_${(0, import_utils17.bytesToHex)((0, import_utils17.randomBytes)(8))}`;
3761
3879
  Error.captureStackTrace(this, this.constructor);
3762
3880
  }
3763
3881
  /**
@@ -3896,9 +4014,9 @@ var AuthFlowError = class extends AuthInternalError {
3896
4014
 
3897
4015
  // libs/auth/src/session/session-crypto.ts
3898
4016
  function getSigningSecret(config) {
3899
- const secret = config?.secret || (0, import_utils16.getEnv)("MCP_SESSION_SECRET");
4017
+ const secret = config?.secret || (0, import_utils18.getEnv)("MCP_SESSION_SECRET");
3900
4018
  if (!secret) {
3901
- if ((0, import_utils16.isProduction)()) {
4019
+ if ((0, import_utils18.isProduction)()) {
3902
4020
  throw new SessionSecretRequiredError("session signing");
3903
4021
  }
3904
4022
  console.warn("[SessionCrypto] MCP_SESSION_SECRET not set. Using insecure default for development only.");
@@ -3910,13 +4028,13 @@ function resolveConfig(config) {
3910
4028
  return { secret: getSigningSecret(config) };
3911
4029
  }
3912
4030
  function signSession(session, config) {
3913
- return (0, import_utils16.signData)(session, resolveConfig(config));
4031
+ return (0, import_utils18.signData)(session, resolveConfig(config));
3914
4032
  }
3915
4033
  function verifySession(signedData, config) {
3916
- return (0, import_utils16.verifyData)(signedData, resolveConfig(config));
4034
+ return (0, import_utils18.verifyData)(signedData, resolveConfig(config));
3917
4035
  }
3918
4036
  function verifyOrParseSession(data, config) {
3919
- return (0, import_utils16.verifyOrParseData)(data, resolveConfig(config));
4037
+ return (0, import_utils18.verifyOrParseData)(data, resolveConfig(config));
3920
4038
  }
3921
4039
 
3922
4040
  // libs/auth/src/session/session-rate-limiter.ts
@@ -4043,7 +4161,7 @@ var SessionRateLimiter = class {
4043
4161
  var defaultSessionRateLimiter = new SessionRateLimiter();
4044
4162
 
4045
4163
  // libs/auth/src/session/session.transport.ts
4046
- var import_utils17 = require("@frontmcp/utils");
4164
+ var import_utils19 = require("@frontmcp/utils");
4047
4165
  var TransportIdGenerator = class {
4048
4166
  /**
4049
4167
  * Create a transport session ID.
@@ -4052,7 +4170,7 @@ var TransportIdGenerator = class {
4052
4170
  * @returns A transport session ID (UUID without dashes)
4053
4171
  */
4054
4172
  static createId() {
4055
- return (0, import_utils17.randomUUID)().replace(/-/g, "");
4173
+ return (0, import_utils19.randomUUID)().replace(/-/g, "");
4056
4174
  }
4057
4175
  };
4058
4176
 
@@ -4101,7 +4219,7 @@ var TinyTtlCache = class {
4101
4219
  };
4102
4220
 
4103
4221
  // libs/auth/src/session/utils/auth-token.utils.ts
4104
- var import_utils18 = require("@frontmcp/utils");
4222
+ var import_utils20 = require("@frontmcp/utils");
4105
4223
  function isJwt(token) {
4106
4224
  if (!token) return false;
4107
4225
  return token.split(".").length === 3;
@@ -4111,7 +4229,7 @@ function getTokenSignatureFingerprint(token) {
4111
4229
  const sig = token.split(".")[2];
4112
4230
  if (sig) return sig;
4113
4231
  }
4114
- return (0, import_utils18.sha256Base64url)(token);
4232
+ return (0, import_utils20.sha256Base64url)(token);
4115
4233
  }
4116
4234
  function extractClaimValue(claims, key, validator) {
4117
4235
  const value = claims[key];
@@ -4143,26 +4261,26 @@ function extractBearerToken(header) {
4143
4261
  }
4144
4262
 
4145
4263
  // libs/auth/src/session/utils/session-crypto.utils.ts
4146
- var import_utils19 = require("@frontmcp/utils");
4264
+ var import_utils21 = require("@frontmcp/utils");
4147
4265
  var cachedKey = null;
4148
4266
  function getKey() {
4149
4267
  if (cachedKey) return cachedKey;
4150
- const secret = (0, import_utils19.getEnv)("MCP_SESSION_SECRET");
4268
+ const secret = (0, import_utils21.getEnv)("MCP_SESSION_SECRET");
4151
4269
  if (!secret) {
4152
- if ((0, import_utils19.isProduction)()) {
4270
+ if ((0, import_utils21.isProduction)()) {
4153
4271
  throw new SessionSecretRequiredError("session ID encryption");
4154
4272
  }
4155
4273
  console.warn(
4156
4274
  "[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption."
4157
4275
  );
4158
4276
  }
4159
- const base = secret || (0, import_utils19.getMachineId)();
4160
- cachedKey = (0, import_utils19.sha256)(new TextEncoder().encode(base));
4277
+ const base = secret || (0, import_utils21.getMachineId)();
4278
+ cachedKey = (0, import_utils21.sha256)(new TextEncoder().encode(base));
4161
4279
  return cachedKey;
4162
4280
  }
4163
4281
  function encryptJson(obj) {
4164
4282
  const key = getKey();
4165
- const encrypted = (0, import_utils19.encryptValue)(obj, key);
4283
+ const encrypted = (0, import_utils21.encryptValue)(obj, key);
4166
4284
  return `${encrypted.iv}.${encrypted.tag}.${encrypted.data}`;
4167
4285
  }
4168
4286
  function decryptSessionJson(sessionId) {
@@ -4171,7 +4289,7 @@ function decryptSessionJson(sessionId) {
4171
4289
  const [ivB64, tagB64, ctB64] = parts;
4172
4290
  if (!ivB64 || !tagB64 || !ctB64) return null;
4173
4291
  const key = getKey();
4174
- return (0, import_utils19.decryptValue)({ alg: "A256GCM", iv: ivB64, tag: tagB64, data: ctB64 }, key);
4292
+ return (0, import_utils21.decryptValue)({ alg: "A256GCM", iv: ivB64, tag: tagB64, data: ctB64 }, key);
4175
4293
  }
4176
4294
  function safeDecrypt(sessionId) {
4177
4295
  try {
@@ -4185,11 +4303,11 @@ function resetCachedKey() {
4185
4303
  }
4186
4304
 
4187
4305
  // libs/auth/src/session/storage/index.ts
4188
- var import_utils23 = require("@frontmcp/utils");
4189
- var import_utils24 = require("@frontmcp/utils");
4306
+ var import_utils25 = require("@frontmcp/utils");
4307
+ var import_utils26 = require("@frontmcp/utils");
4190
4308
 
4191
4309
  // libs/auth/src/session/storage/storage-token-store.ts
4192
- var import_utils20 = require("@frontmcp/utils");
4310
+ var import_utils22 = require("@frontmcp/utils");
4193
4311
  var StorageTokenStore = class {
4194
4312
  storage;
4195
4313
  namespace;
@@ -4201,13 +4319,13 @@ var StorageTokenStore = class {
4201
4319
  this.defaultTtlSeconds = options.defaultTtlSeconds;
4202
4320
  this.storageIsNamespaced = "namespace" in storage && typeof storage.namespace === "function";
4203
4321
  const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
4204
- this.storage = new import_utils20.TypedStorage(namespacedStorage);
4322
+ this.storage = new import_utils22.TypedStorage(namespacedStorage);
4205
4323
  }
4206
4324
  /**
4207
4325
  * Allocate a new unique ID for a token record.
4208
4326
  */
4209
4327
  allocId() {
4210
- return (0, import_utils20.randomUUID)();
4328
+ return (0, import_utils22.randomUUID)();
4211
4329
  }
4212
4330
  /**
4213
4331
  * Store an encrypted token blob.
@@ -4270,7 +4388,7 @@ var StorageTokenStore = class {
4270
4388
  };
4271
4389
 
4272
4390
  // libs/auth/src/session/storage/storage-authorization-vault.ts
4273
- var import_utils21 = require("@frontmcp/utils");
4391
+ var import_utils23 = require("@frontmcp/utils");
4274
4392
  var StorageAuthorizationVault = class {
4275
4393
  storage;
4276
4394
  namespace;
@@ -4279,7 +4397,7 @@ var StorageAuthorizationVault = class {
4279
4397
  this.namespace = options.namespace ?? "vault";
4280
4398
  this.pendingAuthTtlMs = options.pendingAuthTtlMs ?? 10 * 60 * 1e3;
4281
4399
  const namespacedStorage = this.isNamespacedStorage(storage) ? storage.namespace(this.namespace) : storage;
4282
- this.storage = new import_utils21.TypedStorage(namespacedStorage, {
4400
+ this.storage = new import_utils23.TypedStorage(namespacedStorage, {
4283
4401
  schema: options.validateOnRead ? authorizationVaultEntrySchema : void 0,
4284
4402
  throwOnInvalid: false
4285
4403
  });
@@ -4290,7 +4408,7 @@ var StorageAuthorizationVault = class {
4290
4408
  async create(params) {
4291
4409
  const now = Date.now();
4292
4410
  const entry = {
4293
- id: (0, import_utils21.randomUUID)(),
4411
+ id: (0, import_utils23.randomUUID)(),
4294
4412
  userSub: params.userSub,
4295
4413
  userEmail: params.userEmail,
4296
4414
  userName: params.userName,
@@ -4364,7 +4482,7 @@ var StorageAuthorizationVault = class {
4364
4482
  }
4365
4483
  const now = Date.now();
4366
4484
  const pendingAuth = {
4367
- id: (0, import_utils21.randomUUID)(),
4485
+ id: (0, import_utils23.randomUUID)(),
4368
4486
  appId: params.appId,
4369
4487
  toolId: params.toolId,
4370
4488
  authUrl: params.authUrl,
@@ -4568,11 +4686,11 @@ var StorageAuthorizationVault = class {
4568
4686
  };
4569
4687
 
4570
4688
  // libs/auth/src/session/storage/in-memory-authorization-vault.ts
4571
- var import_utils22 = require("@frontmcp/utils");
4689
+ var import_utils24 = require("@frontmcp/utils");
4572
4690
  var InMemoryAuthorizationVault = class extends StorageAuthorizationVault {
4573
4691
  memoryAdapter;
4574
4692
  constructor(options = {}) {
4575
- const memoryAdapter = new import_utils22.MemoryStorageAdapter();
4693
+ const memoryAdapter = new import_utils24.MemoryStorageAdapter();
4576
4694
  super(memoryAdapter, {
4577
4695
  namespace: options.namespace ?? "vault",
4578
4696
  pendingAuthTtlMs: options.pendingAuthTtlMs
@@ -4593,7 +4711,7 @@ var InMemoryAuthorizationVault = class extends StorageAuthorizationVault {
4593
4711
  };
4594
4712
 
4595
4713
  // libs/auth/src/session/redis-session.store.ts
4596
- var import_utils25 = require("@frontmcp/utils");
4714
+ var import_utils27 = require("@frontmcp/utils");
4597
4715
  var RedisSessionStore = class {
4598
4716
  storage;
4599
4717
  keyPrefix;
@@ -4615,14 +4733,14 @@ var RedisSessionStore = class {
4615
4733
  });
4616
4734
  }
4617
4735
  if ("redis" in config && config.redis) {
4618
- this.storage = new import_utils25.RedisStorageAdapter({
4736
+ this.storage = new import_utils27.RedisStorageAdapter({
4619
4737
  client: config.redis,
4620
4738
  keyPrefix: this.keyPrefix
4621
4739
  });
4622
4740
  this.externalInstance = true;
4623
4741
  } else {
4624
4742
  const redisConfig = config;
4625
- this.storage = new import_utils25.RedisStorageAdapter({
4743
+ this.storage = new import_utils27.RedisStorageAdapter({
4626
4744
  config: {
4627
4745
  host: redisConfig.host,
4628
4746
  port: redisConfig.port ?? 6379,
@@ -4804,7 +4922,7 @@ var RedisSessionStore = class {
4804
4922
  * Allocate a new session ID
4805
4923
  */
4806
4924
  allocId() {
4807
- return (0, import_utils25.randomUUID)();
4925
+ return (0, import_utils27.randomUUID)();
4808
4926
  }
4809
4927
  /**
4810
4928
  * Disconnect from Redis (only if we created the connection)
@@ -4840,7 +4958,7 @@ var RedisSessionStore = class {
4840
4958
  };
4841
4959
 
4842
4960
  // libs/auth/src/session/vercel-kv-session.store.ts
4843
- var import_utils26 = require("@frontmcp/utils");
4961
+ var import_utils28 = require("@frontmcp/utils");
4844
4962
  var VercelKvSessionStore = class {
4845
4963
  storage;
4846
4964
  keyPrefix;
@@ -4860,7 +4978,7 @@ var VercelKvSessionStore = class {
4860
4978
  maxRequests: this.security.rateLimiting?.maxRequests
4861
4979
  });
4862
4980
  }
4863
- this.storage = new import_utils26.VercelKvStorageAdapter({
4981
+ this.storage = new import_utils28.VercelKvStorageAdapter({
4864
4982
  url: config.url,
4865
4983
  token: config.token,
4866
4984
  keyPrefix: this.keyPrefix
@@ -5034,7 +5152,7 @@ var VercelKvSessionStore = class {
5034
5152
  * Allocate a new session ID
5035
5153
  */
5036
5154
  allocId() {
5037
- return (0, import_utils26.randomUUID)();
5155
+ return (0, import_utils28.randomUUID)();
5038
5156
  }
5039
5157
  /**
5040
5158
  * Disconnect from Vercel KV
@@ -5063,7 +5181,7 @@ var VercelKvSessionStore = class {
5063
5181
  };
5064
5182
 
5065
5183
  // libs/auth/src/session/orchestrated-token.store.ts
5066
- var import_utils27 = require("@frontmcp/utils");
5184
+ var import_utils29 = require("@frontmcp/utils");
5067
5185
  var InMemoryOrchestratedTokenStore = class {
5068
5186
  /** Token storage: Map<compositeKey, ProviderTokenRecord> */
5069
5187
  tokens = /* @__PURE__ */ new Map();
@@ -5105,7 +5223,7 @@ var InMemoryOrchestratedTokenStore = class {
5105
5223
  }
5106
5224
  const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
5107
5225
  const salt = new TextEncoder().encode("frontmcp-token-store");
5108
- const derivedKey = (0, import_utils27.hkdfSha256)(this.encryptionKey, salt, info, 32);
5226
+ const derivedKey = (0, import_utils29.hkdfSha256)(this.encryptionKey, salt, info, 32);
5109
5227
  this.derivedKeys.set(compositeKey, derivedKey);
5110
5228
  return derivedKey;
5111
5229
  }
@@ -5115,8 +5233,8 @@ var InMemoryOrchestratedTokenStore = class {
5115
5233
  async encryptRecord(compositeKey, record) {
5116
5234
  const key = await this.deriveKeyForRecord(compositeKey);
5117
5235
  const plaintext = JSON.stringify(record);
5118
- const iv = (0, import_utils27.randomBytes)(12);
5119
- const { ciphertext, tag } = (0, import_utils27.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
5236
+ const iv = (0, import_utils29.randomBytes)(12);
5237
+ const { ciphertext, tag } = (0, import_utils29.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
5120
5238
  return JSON.stringify({
5121
5239
  iv: Buffer.from(iv).toString("base64url"),
5122
5240
  tag: Buffer.from(tag).toString("base64url"),
@@ -5132,7 +5250,7 @@ var InMemoryOrchestratedTokenStore = class {
5132
5250
  const ivBytes = Buffer.from(iv, "base64url");
5133
5251
  const tagBytes = Buffer.from(tag, "base64url");
5134
5252
  const ciphertextBytes = Buffer.from(data, "base64url");
5135
- const plaintext = (0, import_utils27.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
5253
+ const plaintext = (0, import_utils29.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
5136
5254
  return JSON.parse(new TextDecoder().decode(plaintext));
5137
5255
  }
5138
5256
  /**
@@ -5334,7 +5452,7 @@ var InMemoryOrchestratedTokenStore = class {
5334
5452
  };
5335
5453
 
5336
5454
  // libs/auth/src/session/orchestrated-token.crypto.ts
5337
- var import_utils28 = require("@frontmcp/utils");
5455
+ var import_utils30 = require("@frontmcp/utils");
5338
5456
  var HKDF_SALT = new TextEncoder().encode("frontmcp-token-store");
5339
5457
  function deriveKeyForRecord(masterKey, compositeKey, cache) {
5340
5458
  const cached = cache.get(compositeKey);
@@ -5342,14 +5460,14 @@ function deriveKeyForRecord(masterKey, compositeKey, cache) {
5342
5460
  return cached;
5343
5461
  }
5344
5462
  const info = new TextEncoder().encode(`orchestrated-token:${compositeKey}`);
5345
- const derived = (0, import_utils28.hkdfSha256)(masterKey, HKDF_SALT, info, 32);
5463
+ const derived = (0, import_utils30.hkdfSha256)(masterKey, HKDF_SALT, info, 32);
5346
5464
  cache.set(compositeKey, derived);
5347
5465
  return derived;
5348
5466
  }
5349
5467
  function encryptRecord(key, record) {
5350
5468
  const plaintext = JSON.stringify(record);
5351
- const iv = (0, import_utils28.randomBytes)(12);
5352
- const { ciphertext, tag } = (0, import_utils28.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
5469
+ const iv = (0, import_utils30.randomBytes)(12);
5470
+ const { ciphertext, tag } = (0, import_utils30.encryptAesGcm)(key, new TextEncoder().encode(plaintext), iv);
5353
5471
  return JSON.stringify({
5354
5472
  iv: Buffer.from(iv).toString("base64url"),
5355
5473
  tag: Buffer.from(tag).toString("base64url"),
@@ -5361,7 +5479,7 @@ function decryptRecord(key, encrypted) {
5361
5479
  const ivBytes = Buffer.from(iv, "base64url");
5362
5480
  const tagBytes = Buffer.from(tag, "base64url");
5363
5481
  const ciphertextBytes = Buffer.from(data, "base64url");
5364
- const plaintext = (0, import_utils28.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
5482
+ const plaintext = (0, import_utils30.decryptAesGcm)(key, ciphertextBytes, ivBytes, tagBytes);
5365
5483
  return JSON.parse(new TextDecoder().decode(plaintext));
5366
5484
  }
5367
5485
 
@@ -5540,7 +5658,7 @@ var StorageOrchestratedTokenStore = class {
5540
5658
  };
5541
5659
 
5542
5660
  // libs/auth/src/session/federated-auth.session.ts
5543
- var import_utils29 = require("@frontmcp/utils");
5661
+ var import_utils31 = require("@frontmcp/utils");
5544
5662
  function toSessionRecord(session) {
5545
5663
  return {
5546
5664
  ...session,
@@ -5615,7 +5733,7 @@ var InMemoryFederatedAuthSessionStore = class {
5615
5733
  createSession(params) {
5616
5734
  const now = Date.now();
5617
5735
  return {
5618
- id: (0, import_utils29.randomUUID)(),
5736
+ id: (0, import_utils31.randomUUID)(),
5619
5737
  pendingAuthId: params.pendingAuthId,
5620
5738
  clientId: params.clientId,
5621
5739
  redirectUri: params.redirectUri,
@@ -5627,6 +5745,7 @@ var InMemoryFederatedAuthSessionStore = class {
5627
5745
  providerQueue: [...params.providerIds],
5628
5746
  completedProviders: /* @__PURE__ */ new Map(),
5629
5747
  skippedProviders: [],
5748
+ signinBinding: params.signinBinding,
5630
5749
  createdAt: now,
5631
5750
  expiresAt: now + this.sessionTtlMs
5632
5751
  };
@@ -5647,7 +5766,7 @@ var InMemoryFederatedAuthSessionStore = class {
5647
5766
  function createFederatedAuthSession(params, ttlMs = 15 * 60 * 1e3) {
5648
5767
  const now = Date.now();
5649
5768
  return {
5650
- id: (0, import_utils29.randomUUID)(),
5769
+ id: (0, import_utils31.randomUUID)(),
5651
5770
  pendingAuthId: params.pendingAuthId,
5652
5771
  clientId: params.clientId,
5653
5772
  redirectUri: params.redirectUri,
@@ -5659,6 +5778,7 @@ function createFederatedAuthSession(params, ttlMs = 15 * 60 * 1e3) {
5659
5778
  providerQueue: [...params.providerIds],
5660
5779
  completedProviders: /* @__PURE__ */ new Map(),
5661
5780
  skippedProviders: [],
5781
+ signinBinding: params.signinBinding,
5662
5782
  createdAt: now,
5663
5783
  expiresAt: now + ttlMs
5664
5784
  };
@@ -5704,7 +5824,7 @@ function startNextProvider(session, pkce, state) {
5704
5824
  }
5705
5825
 
5706
5826
  // libs/auth/src/session/storage-federated-auth.session.ts
5707
- var import_utils30 = require("@frontmcp/utils");
5827
+ var import_utils32 = require("@frontmcp/utils");
5708
5828
  var StorageFederatedAuthSessionStore = class {
5709
5829
  storage;
5710
5830
  namespace;
@@ -5713,7 +5833,7 @@ var StorageFederatedAuthSessionStore = class {
5713
5833
  this.namespace = options.namespace ?? "fed";
5714
5834
  this.storageIsNamespaced = this.isNamespacedStorage(storage);
5715
5835
  const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
5716
- this.storage = new import_utils30.TypedStorage(namespacedStorage);
5836
+ this.storage = new import_utils32.TypedStorage(namespacedStorage);
5717
5837
  }
5718
5838
  async store(session) {
5719
5839
  await this.put(session);
@@ -5784,7 +5904,7 @@ var InMemoryConsentStore = class {
5784
5904
  };
5785
5905
 
5786
5906
  // libs/auth/src/session/storage-consent.store.ts
5787
- var import_utils31 = require("@frontmcp/utils");
5907
+ var import_utils33 = require("@frontmcp/utils");
5788
5908
  var StorageConsentStore = class {
5789
5909
  storage;
5790
5910
  namespace;
@@ -5793,7 +5913,7 @@ var StorageConsentStore = class {
5793
5913
  this.namespace = options.namespace ?? "remembered-consent";
5794
5914
  this.storageIsNamespaced = this.isNamespacedStorage(storage);
5795
5915
  const namespacedStorage = this.storageIsNamespaced ? storage.namespace(this.namespace) : storage;
5796
- this.storage = new import_utils31.TypedStorage(namespacedStorage);
5916
+ this.storage = new import_utils33.TypedStorage(namespacedStorage);
5797
5917
  }
5798
5918
  async get(userSub, clientId) {
5799
5919
  return this.storage.get(this.key(userSub, clientId));
@@ -5815,7 +5935,7 @@ var StorageConsentStore = class {
5815
5935
 
5816
5936
  // libs/auth/src/session/encrypted-authorization-vault.ts
5817
5937
  var import_lazy_zod5 = require("@frontmcp/lazy-zod");
5818
- var import_utils32 = require("@frontmcp/utils");
5938
+ var import_utils34 = require("@frontmcp/utils");
5819
5939
  var redisVaultEntrySchema = import_lazy_zod5.z.object({
5820
5940
  /** Vault ID */
5821
5941
  id: import_lazy_zod5.z.string(),
@@ -5840,7 +5960,7 @@ var redisVaultEntrySchema = import_lazy_zod5.z.object({
5840
5960
  /** Encrypted sensitive data blob */
5841
5961
  encrypted: encryptedDataSchema
5842
5962
  });
5843
- var encryptionContextStorage = new import_utils32.AsyncLocalStorage();
5963
+ var encryptionContextStorage = new import_utils34.AsyncLocalStorage();
5844
5964
  var EncryptedRedisVault = class {
5845
5965
  constructor(redis, encryption, namespace = "vault:") {
5846
5966
  this.redis = redis;
@@ -5976,7 +6096,7 @@ var EncryptedRedisVault = class {
5976
6096
  async create(params) {
5977
6097
  const now = Date.now();
5978
6098
  const entry = {
5979
- id: (0, import_utils32.randomUUID)(),
6099
+ id: (0, import_utils34.randomUUID)(),
5980
6100
  userSub: params.userSub,
5981
6101
  userEmail: params.userEmail,
5982
6102
  userName: params.userName,
@@ -6035,7 +6155,7 @@ var EncryptedRedisVault = class {
6035
6155
  }
6036
6156
  const now = Date.now();
6037
6157
  const pendingAuth = {
6038
- id: (0, import_utils32.randomUUID)(),
6158
+ id: (0, import_utils34.randomUUID)(),
6039
6159
  appId: params.appId,
6040
6160
  toolId: params.toolId,
6041
6161
  authUrl: params.authUrl,
@@ -6213,7 +6333,7 @@ function createEncryptedVault(redis, config = {}) {
6213
6333
  }
6214
6334
 
6215
6335
  // libs/auth/src/session/token.refresh.ts
6216
- var import_utils33 = require("@frontmcp/utils");
6336
+ var import_utils35 = require("@frontmcp/utils");
6217
6337
  function toEpochSeconds(exp) {
6218
6338
  if (exp == null) return void 0;
6219
6339
  if (exp instanceof Date) return Math.floor(exp.getTime() / 1e3);
@@ -6235,7 +6355,7 @@ function tryJwtExp(token) {
6235
6355
  const parts = token.split(".");
6236
6356
  if (parts.length < 2) return void 0;
6237
6357
  try {
6238
- const json = JSON.parse(new TextDecoder().decode((0, import_utils33.base64urlDecode)(parts[1])));
6358
+ const json = JSON.parse(new TextDecoder().decode((0, import_utils35.base64urlDecode)(parts[1])));
6239
6359
  const e = json?.exp;
6240
6360
  return typeof e === "number" ? toEpochSeconds(e) : void 0;
6241
6361
  } catch {
@@ -6298,7 +6418,7 @@ var progressiveAuthStateSchema = import_lazy_zod6.z.object({
6298
6418
  });
6299
6419
 
6300
6420
  // libs/auth/src/authorization/authorization.class.ts
6301
- var import_utils36 = require("@frontmcp/utils");
6421
+ var import_utils38 = require("@frontmcp/utils");
6302
6422
  var AuthorizationBase = class _AuthorizationBase {
6303
6423
  id;
6304
6424
  isAnonymous;
@@ -6346,14 +6466,14 @@ var AuthorizationBase = class _AuthorizationBase {
6346
6466
  * @param fingerprint - Optional client fingerprint for tracking
6347
6467
  */
6348
6468
  createTransportSession(protocol, fingerprint) {
6349
- const sessionId = (0, import_utils36.randomUUID)();
6469
+ const sessionId = (0, import_utils38.randomUUID)();
6350
6470
  const session = {
6351
6471
  id: sessionId,
6352
6472
  authorizationId: this.id,
6353
6473
  protocol,
6354
6474
  createdAt: Date.now(),
6355
6475
  expiresAt: this.expiresAt,
6356
- nodeId: (0, import_utils36.getMachineId)(),
6476
+ nodeId: (0, import_utils38.getMachineId)(),
6357
6477
  clientFingerprint: fingerprint
6358
6478
  };
6359
6479
  this.#sessions.set(session.id, session);
@@ -6494,7 +6614,7 @@ var AuthorizationBase = class _AuthorizationBase {
6494
6614
  */
6495
6615
  static isJwtHeader(segment) {
6496
6616
  try {
6497
- const bytes = (0, import_utils36.base64urlDecode)(segment);
6617
+ const bytes = (0, import_utils38.base64urlDecode)(segment);
6498
6618
  const text = new TextDecoder().decode(bytes);
6499
6619
  const parsed = JSON.parse(text);
6500
6620
  return typeof parsed === "object" && parsed !== null && typeof parsed.alg === "string";
@@ -6546,7 +6666,7 @@ var AuthorizationBase = class _AuthorizationBase {
6546
6666
  };
6547
6667
 
6548
6668
  // libs/auth/src/authorization/public.authorization.ts
6549
- var import_utils37 = require("@frontmcp/utils");
6669
+ var import_utils39 = require("@frontmcp/utils");
6550
6670
  var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
6551
6671
  mode = "public";
6552
6672
  /**
@@ -6582,7 +6702,7 @@ var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
6582
6702
  allowedTools = "all",
6583
6703
  allowedPrompts = "all"
6584
6704
  } = ctx;
6585
- const uuid = (0, import_utils37.randomUUID)();
6705
+ const uuid = (0, import_utils39.randomUUID)();
6586
6706
  const sub = `${prefix}:${uuid}`;
6587
6707
  const user = {
6588
6708
  sub,
@@ -6666,7 +6786,7 @@ var PublicAuthorization = class _PublicAuthorization extends AuthorizationBase {
6666
6786
  };
6667
6787
 
6668
6788
  // libs/auth/src/authorization/transparent.authorization.ts
6669
- var import_utils38 = require("@frontmcp/utils");
6789
+ var import_utils40 = require("@frontmcp/utils");
6670
6790
  var TransparentAuthorization = class _TransparentAuthorization extends AuthorizationBase {
6671
6791
  mode = "transparent";
6672
6792
  /**
@@ -6766,7 +6886,7 @@ var TransparentAuthorization = class _TransparentAuthorization extends Authoriza
6766
6886
  static generateAuthorizationId(token) {
6767
6887
  const parts = token.split(".");
6768
6888
  const signature = parts[2] || token;
6769
- return (0, import_utils38.sha256Hex)(signature).substring(0, 16);
6889
+ return (0, import_utils40.sha256Hex)(signature).substring(0, 16);
6770
6890
  }
6771
6891
  /**
6772
6892
  * Get the issuer from the token claims
@@ -6792,11 +6912,11 @@ var TransparentAuthorization = class _TransparentAuthorization extends Authoriza
6792
6912
  };
6793
6913
 
6794
6914
  // libs/auth/src/utils/authorization-id.utils.ts
6795
- var import_utils39 = require("@frontmcp/utils");
6915
+ var import_utils41 = require("@frontmcp/utils");
6796
6916
  function deriveAuthorizationId(token) {
6797
6917
  const parts = token.split(".");
6798
6918
  const signature = parts[2] || token;
6799
- return (0, import_utils39.sha256Hex)(signature).substring(0, 16);
6919
+ return (0, import_utils41.sha256Hex)(signature).substring(0, 16);
6800
6920
  }
6801
6921
 
6802
6922
  // libs/auth/src/authorization/orchestrated.authorization.ts
@@ -7165,7 +7285,7 @@ var OrchestratedAuthAccessorAdapter = class {
7165
7285
  };
7166
7286
 
7167
7287
  // libs/auth/src/authorization/dcr-client.registry.ts
7168
- var import_utils40 = require("@frontmcp/utils");
7288
+ var import_utils42 = require("@frontmcp/utils");
7169
7289
  var DEFAULT_MAX_DYNAMIC_CLIENTS = 1e3;
7170
7290
  function globMatch(pattern, input) {
7171
7291
  let p = 0;
@@ -7291,7 +7411,7 @@ var DcrClientRegistry = class {
7291
7411
  if (!isConfidential) return "ok";
7292
7412
  if (!client.client_secret) return "invalid";
7293
7413
  if (!presentedSecret) return "invalid";
7294
- return (0, import_utils40.timingSafeEqual)((0, import_utils40.sha256)(presentedSecret), (0, import_utils40.sha256)(client.client_secret)) ? "ok" : "invalid";
7414
+ return (0, import_utils42.timingSafeEqual)((0, import_utils42.sha256)(presentedSecret), (0, import_utils42.sha256)(client.client_secret)) ? "ok" : "invalid";
7295
7415
  }
7296
7416
  /** Whether a client id is known to the registry. */
7297
7417
  has(clientId) {
@@ -7379,7 +7499,7 @@ var DcrClientRegistry = class {
7379
7499
  if (typeof presented !== "string" || presented.length === 0) {
7380
7500
  return false;
7381
7501
  }
7382
- return (0, import_utils40.timingSafeEqual)((0, import_utils40.sha256)(presented), (0, import_utils40.sha256)(expected));
7502
+ return (0, import_utils42.timingSafeEqual)((0, import_utils42.sha256)(presented), (0, import_utils42.sha256)(expected));
7383
7503
  }
7384
7504
  };
7385
7505
 
@@ -7708,7 +7828,7 @@ var cimdConfigSchema = import_lazy_zod9.z.object({
7708
7828
  init_cimd_cache();
7709
7829
 
7710
7830
  // libs/auth/src/cimd/cimd.service.ts
7711
- var import_utils41 = require("@frontmcp/utils");
7831
+ var import_utils43 = require("@frontmcp/utils");
7712
7832
  init_cimd_cache();
7713
7833
  var CimdService = class {
7714
7834
  config;
@@ -7883,7 +8003,7 @@ var CimdService = class {
7883
8003
  httpStatus: 304
7884
8004
  });
7885
8005
  }
7886
- if ((0, import_utils41.isRedirectResponse)(response)) {
8006
+ if ((0, import_utils43.isRedirectResponse)(response)) {
7887
8007
  const location = response.headers.get("location");
7888
8008
  if (!location) {
7889
8009
  throw new CimdFetchError(clientId, "Redirect response missing Location header", {
@@ -8196,7 +8316,16 @@ var upstreamProviderSchema = import_lazy_zod10.z.object({
8196
8316
  /** User info endpoint (optional, used to enrich the session identity). */
8197
8317
  userInfoEndpoint: import_lazy_zod10.z.string().url().optional(),
8198
8318
  /** JWKS URI for upstream id_token validation (optional). */
8199
- jwksUri: import_lazy_zod10.z.string().url().optional()
8319
+ jwksUri: import_lazy_zod10.z.string().url().optional(),
8320
+ /**
8321
+ * The provider's issuer identifier (its OIDC `iss`). When set, an RFC 9207
8322
+ * `iss` on the provider's callback and the `iss` of its `id_token` must name
8323
+ * it (or one of {@link additionalIssuers}). Without it the provider's
8324
+ * `id_token` is not used for identity; `userInfoEndpoint` is asked instead.
8325
+ */
8326
+ issuer: import_lazy_zod10.z.string().url().optional(),
8327
+ /** Other issuer values this provider legitimately uses. */
8328
+ additionalIssuers: import_lazy_zod10.z.array(import_lazy_zod10.z.string().trim().min(1, "additionalIssuers entries must be non-empty")).optional()
8200
8329
  }).refine((p) => !!(p.authorizationEndpoint ?? p.authorizeUrl), {
8201
8330
  message: "authorizationEndpoint (or authorizeUrl) is required",
8202
8331
  path: ["authorizationEndpoint"]
@@ -8605,17 +8734,21 @@ var sharedAuthFields = {
8605
8734
  * Require the OAuth client to be KNOWN (registered via DCR / pre-registered,
8606
8735
  * or a CIMD client-id URL) before an authorization request is accepted.
8607
8736
  *
8608
- * @default false (backward compatible — unknown client ids are accepted).
8737
+ * @default true
8609
8738
  *
8610
- * SECURITY: with the default, an unregistered client id has no trusted
8611
- * `redirect_uris` to validate against, so its (attacker-chosen) redirect_uri
8612
- * is accepted and a real authorization code can be delivered to an attacker
8613
- * (auth-code interception → account takeover in real-IdP modes). Set this to
8614
- * `true` (recommended for production) to enforce OAuth 2.1 exact redirect-uri
8615
- * matching for every client. Clients on a configured
8616
- * `dcr.allowedRedirectUris` allowlist still pass.
8617
- */
8618
- requireRegisteredClients: import_lazy_zod14.z.boolean().default(false),
8739
+ * SECURITY: an unregistered client id has no trusted `redirect_uris` to
8740
+ * validate against, so accepting it lets anyone start a sign-in whose
8741
+ * authorization code is delivered to a redirect_uri of their choosing
8742
+ * (auth-code interception). `false` is for local development only; a
8743
+ * `dcr.allowedRedirectUris` allowlist still constrains such clients.
8744
+ */
8745
+ requireRegisteredClients: import_lazy_zod14.z.boolean().default(true),
8746
+ /**
8747
+ * Scopes the server may grant (exact values or `*` globs). Requested scopes
8748
+ * outside this list are dropped from the grant. Unset → the standard OpenID
8749
+ * scopes only (see `DEFAULT_ALLOWED_SCOPES`).
8750
+ */
8751
+ allowedScopes: import_lazy_zod14.z.array(import_lazy_zod14.z.string().trim().min(1, "allowedScopes entries must be non-empty")).optional(),
8619
8752
  cimd: cimdConfigSchema.optional(),
8620
8753
  // #469 — custom auth UI as a slot→file map + extras name→handler map (per-app
8621
8754
  // under splitByApp). Both optional; omitting them serves the built-in pages.
@@ -9226,6 +9359,50 @@ var AudienceValidator = class _AudienceValidator {
9226
9359
  }
9227
9360
  };
9228
9361
 
9362
+ // libs/auth/src/utils/scope-grant.utils.ts
9363
+ var DEFAULT_ALLOWED_SCOPES = Object.freeze([
9364
+ "openid",
9365
+ "profile",
9366
+ "email",
9367
+ "offline_access"
9368
+ ]);
9369
+ function grantScopes(requested, allowed) {
9370
+ const patterns = allowed ?? DEFAULT_ALLOWED_SCOPES;
9371
+ const granted = [];
9372
+ for (const scope of requested) {
9373
+ if (!scope || granted.includes(scope)) continue;
9374
+ if (patterns.some((pattern) => globMatch(pattern, scope))) granted.push(scope);
9375
+ }
9376
+ return granted;
9377
+ }
9378
+ function advertisedScopes(allowed) {
9379
+ const scopes = [];
9380
+ for (const scope of allowed ?? DEFAULT_ALLOWED_SCOPES) {
9381
+ if (scope && !scope.includes("*") && !scopes.includes(scope)) scopes.push(scope);
9382
+ }
9383
+ return scopes;
9384
+ }
9385
+
9386
+ // libs/auth/src/utils/redirect-uri.utils.ts
9387
+ function isLoopbackRedirectUri(uri) {
9388
+ let url;
9389
+ try {
9390
+ url = new URL(uri);
9391
+ } catch {
9392
+ return false;
9393
+ }
9394
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
9395
+ if (url.username || url.password) return false;
9396
+ const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
9397
+ if (host === "localhost" || host === "::1") return true;
9398
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
9399
+ if (m) {
9400
+ const octets = m.slice(1).map((n) => Number(n));
9401
+ return octets.every((o) => o >= 0 && o <= 255) && octets[0] === 127;
9402
+ }
9403
+ return false;
9404
+ }
9405
+
9229
9406
  // libs/auth/src/vault/auth-providers.types.ts
9230
9407
  var import_lazy_zod19 = require("@frontmcp/lazy-zod");
9231
9408
  var credentialScopeSchema = import_lazy_zod19.z.enum(["global", "user", "session"]);
@@ -9468,7 +9645,7 @@ var CredentialCache = class {
9468
9645
  var AUTH_PROVIDERS_ACCESSOR = /* @__PURE__ */ Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");
9469
9646
 
9470
9647
  // libs/auth/src/vault/auth-providers.accessor.impl.ts
9471
- var import_utils44 = require("@frontmcp/utils");
9648
+ var import_utils46 = require("@frontmcp/utils");
9472
9649
  var AuthProvidersAccessorImpl = class {
9473
9650
  constructor(registry, vault, cache, loader, context, logger) {
9474
9651
  this.registry = registry;
@@ -9683,7 +9860,7 @@ var AuthProvidersAccessorImpl = class {
9683
9860
  return { [credential.headerName]: credential.key };
9684
9861
  case "basic": {
9685
9862
  const value = `${credential.username}:${credential.password}`;
9686
- const encoded = credential.encodedValue ?? (0, import_utils44.base64Encode)(new TextEncoder().encode(value));
9863
+ const encoded = credential.encodedValue ?? (0, import_utils46.base64Encode)(new TextEncoder().encode(value));
9687
9864
  return { Authorization: `Basic ${encoded}` };
9688
9865
  }
9689
9866
  case "custom":
@@ -10791,6 +10968,211 @@ async function evaluateRebac(policy, ctx) {
10791
10968
  return { granted: true, evaluatedPolicies: ["relationships"] };
10792
10969
  }
10793
10970
 
10971
+ // libs/auth/src/authorities/authorities.validation.ts
10972
+ var ABAC_OPERATORS = /* @__PURE__ */ new Set([
10973
+ "eq",
10974
+ "neq",
10975
+ "in",
10976
+ "notIn",
10977
+ "gt",
10978
+ "gte",
10979
+ "lt",
10980
+ "lte",
10981
+ "contains",
10982
+ "startsWith",
10983
+ "endsWith",
10984
+ "exists",
10985
+ "matches"
10986
+ ]);
10987
+ var CHECK_FIELDS = [
10988
+ "roles",
10989
+ "permissions",
10990
+ "attributes",
10991
+ "relationships",
10992
+ "custom",
10993
+ "guards",
10994
+ "allOf",
10995
+ "anyOf",
10996
+ "not"
10997
+ ];
10998
+ var RULE_FIELDS = /* @__PURE__ */ new Set([...CHECK_FIELDS, "operator"]);
10999
+ function isPlainObject(value) {
11000
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11001
+ }
11002
+ function isNonEmptyString(value) {
11003
+ return typeof value === "string" && value.length > 0;
11004
+ }
11005
+ function unknownFields(value, known, path) {
11006
+ const prefix = path ? `${path} has` : "has";
11007
+ return Object.keys(value).filter((key) => !known.has(key)).map((key) => `${prefix} an unknown field "${key}"`);
11008
+ }
11009
+ function checkStringList(value, path) {
11010
+ if (!Array.isArray(value)) return [`${path} must be a list of names`];
11011
+ if (value.length === 0) return [`${path} is empty`];
11012
+ return value.every(isNonEmptyString) ? [] : [`${path} must contain only non-empty names`];
11013
+ }
11014
+ var ALL_OR_ANY = /* @__PURE__ */ new Set(["all", "any"]);
11015
+ function checkAllOrAny(value, path) {
11016
+ if (!isPlainObject(value)) return [`${path} must be an object with "all" or "any"`];
11017
+ const problems = unknownFields(value, ALL_OR_ANY, path);
11018
+ if (value["all"] === void 0 && value["any"] === void 0) {
11019
+ problems.push(`${path} needs "all" or "any"`);
11020
+ }
11021
+ if (value["all"] !== void 0) problems.push(...checkStringList(value["all"], `${path}.all`));
11022
+ if (value["any"] !== void 0) problems.push(...checkStringList(value["any"], `${path}.any`));
11023
+ return problems;
11024
+ }
11025
+ var ATTRIBUTE_FIELDS = /* @__PURE__ */ new Set(["match", "conditions"]);
11026
+ var CONDITION_FIELDS = /* @__PURE__ */ new Set(["path", "op", "value"]);
11027
+ function isValueReference(value) {
11028
+ return isPlainObject(value) && ("fromInput" in value || "fromClaims" in value);
11029
+ }
11030
+ function checkValueReference(value, path) {
11031
+ const ref = value["fromInput"] ?? value["fromClaims"];
11032
+ return Object.keys(value).length === 1 && isNonEmptyString(ref) ? [] : [`${path} must be { fromInput: "<name>" } or { fromClaims: "<path>" }`];
11033
+ }
11034
+ var OPERATOR_VALUES = {
11035
+ in: { accepts: Array.isArray, needs: "a list" },
11036
+ notIn: { accepts: Array.isArray, needs: "a list" },
11037
+ gt: { accepts: (value) => typeof value === "number", needs: "a number" },
11038
+ gte: { accepts: (value) => typeof value === "number", needs: "a number" },
11039
+ lt: { accepts: (value) => typeof value === "number", needs: "a number" },
11040
+ lte: { accepts: (value) => typeof value === "number", needs: "a number" },
11041
+ startsWith: { accepts: (value) => typeof value === "string", needs: "a string" },
11042
+ endsWith: { accepts: (value) => typeof value === "string", needs: "a string" },
11043
+ matches: { accepts: (value) => typeof value === "string", needs: "a string" }
11044
+ };
11045
+ function checkConditionValue(op, condition, path) {
11046
+ const value = condition["value"];
11047
+ if (value === void 0) return [`${path} is missing`];
11048
+ if (op === "exists") {
11049
+ return typeof value === "boolean" ? [] : [`${path} must be true or false for "exists"`];
11050
+ }
11051
+ if (isValueReference(value)) return checkValueReference(value, path);
11052
+ const expected = OPERATOR_VALUES[op];
11053
+ if (expected && !expected.accepts(value)) return [`${path} must be ${expected.needs} for "${op}"`];
11054
+ if (Array.isArray(value) && value.length === 0 && (op === "in" || op === "notIn")) return [`${path} is empty`];
11055
+ return [];
11056
+ }
11057
+ function checkAttributes(value, path) {
11058
+ if (!isPlainObject(value)) return [`${path} must be an object with "match" or "conditions"`];
11059
+ const problems = unknownFields(value, ATTRIBUTE_FIELDS, path);
11060
+ const { match, conditions } = value;
11061
+ if (match === void 0 && conditions === void 0) {
11062
+ problems.push(`${path} needs "match" or "conditions"`);
11063
+ }
11064
+ if (match !== void 0) {
11065
+ if (!isPlainObject(match)) problems.push(`${path}.match must be an object`);
11066
+ else if (Object.keys(match).length === 0) problems.push(`${path}.match is empty`);
11067
+ else {
11068
+ for (const [attribute, expected] of Object.entries(match)) {
11069
+ const at = `${path}.match[${JSON.stringify(attribute)}]`;
11070
+ if (expected === void 0) problems.push(`${at} has no value`);
11071
+ else if (isValueReference(expected)) problems.push(...checkValueReference(expected, at));
11072
+ }
11073
+ }
11074
+ }
11075
+ if (conditions !== void 0) {
11076
+ if (!Array.isArray(conditions)) problems.push(`${path}.conditions must be a list`);
11077
+ else if (conditions.length === 0) problems.push(`${path}.conditions is empty`);
11078
+ else {
11079
+ conditions.forEach((condition, index) => {
11080
+ const at = `${path}.conditions[${index}]`;
11081
+ if (!isPlainObject(condition)) {
11082
+ problems.push(`${at} must be an object with "path" and "op"`);
11083
+ return;
11084
+ }
11085
+ problems.push(...unknownFields(condition, CONDITION_FIELDS, at));
11086
+ if (!isNonEmptyString(condition["path"])) problems.push(`${at}.path must be a non-empty string`);
11087
+ const op = condition["op"];
11088
+ if (!ABAC_OPERATORS.has(op)) {
11089
+ problems.push(`${at}.op "${String(condition["op"])}" is not an operator`);
11090
+ } else {
11091
+ problems.push(...checkConditionValue(op, condition, `${at}.value`));
11092
+ }
11093
+ });
11094
+ }
11095
+ }
11096
+ return problems;
11097
+ }
11098
+ var RELATIONSHIP_FIELDS = /* @__PURE__ */ new Set(["type", "resource", "resourceId"]);
11099
+ function checkResourceId(value, path) {
11100
+ if (isNonEmptyString(value)) return [];
11101
+ if (isPlainObject(value) && Object.keys(value).length === 1) {
11102
+ const ref = value["fromInput"] ?? value["fromClaims"];
11103
+ if (isNonEmptyString(ref)) return [];
11104
+ }
11105
+ return [`${path} must be a string, { fromInput } or { fromClaims }`];
11106
+ }
11107
+ function checkRelationship(value, path) {
11108
+ if (!isPlainObject(value)) return [`${path} must be an object with "type", "resource" and "resourceId"`];
11109
+ const problems = unknownFields(value, RELATIONSHIP_FIELDS, path);
11110
+ if (!isNonEmptyString(value["type"])) problems.push(`${path}.type must be a non-empty string`);
11111
+ if (!isNonEmptyString(value["resource"])) problems.push(`${path}.resource must be a non-empty string`);
11112
+ problems.push(...checkResourceId(value["resourceId"], `${path}.resourceId`));
11113
+ return problems;
11114
+ }
11115
+ function checkRelationships(value, path) {
11116
+ if (!Array.isArray(value)) return checkRelationship(value, path);
11117
+ if (value.length === 0) return [`${path} is empty`];
11118
+ return value.flatMap((relationship, index) => checkRelationship(relationship, `${path}[${index}]`));
11119
+ }
11120
+ function checkRuleList(value, path) {
11121
+ if (!Array.isArray(value)) return [`${path} must be a list of rules`];
11122
+ if (value.length === 0) return [`${path} is empty`];
11123
+ return value.flatMap((rule, index) => checkRule(rule, `${path}[${index}]`));
11124
+ }
11125
+ function checkRule(rule, path) {
11126
+ if (!isPlainObject(rule)) {
11127
+ return [`${path || "the rule"} must be a rule object${typeof rule === "string" ? ", not a profile name" : ""}`];
11128
+ }
11129
+ const problems = unknownFields(rule, RULE_FIELDS, path);
11130
+ if (!CHECK_FIELDS.some((field) => rule[field] !== void 0)) {
11131
+ problems.push(`${path ? `${path} ` : ""}checks nothing`);
11132
+ }
11133
+ if (rule["operator"] !== void 0 && rule["operator"] !== "AND" && rule["operator"] !== "OR") {
11134
+ problems.push(`${path}.operator must be "AND" or "OR"`);
11135
+ }
11136
+ if (rule["roles"] !== void 0) problems.push(...checkAllOrAny(rule["roles"], `${path}.roles`));
11137
+ if (rule["permissions"] !== void 0) problems.push(...checkAllOrAny(rule["permissions"], `${path}.permissions`));
11138
+ if (rule["attributes"] !== void 0) problems.push(...checkAttributes(rule["attributes"], `${path}.attributes`));
11139
+ if (rule["relationships"] !== void 0) {
11140
+ problems.push(...checkRelationships(rule["relationships"], `${path}.relationships`));
11141
+ }
11142
+ if (rule["custom"] !== void 0) {
11143
+ if (!isPlainObject(rule["custom"])) problems.push(`${path}.custom must be an object`);
11144
+ else if (Object.keys(rule["custom"]).length === 0) problems.push(`${path}.custom is empty`);
11145
+ }
11146
+ if (rule["guards"] !== void 0) {
11147
+ const guards = rule["guards"];
11148
+ if (!Array.isArray(guards)) problems.push(`${path}.guards must be a list of functions`);
11149
+ else if (guards.length === 0) problems.push(`${path}.guards is empty`);
11150
+ else if (!guards.every((guard) => typeof guard === "function")) {
11151
+ problems.push(`${path}.guards must contain only functions`);
11152
+ }
11153
+ }
11154
+ if (rule["allOf"] !== void 0) problems.push(...checkRuleList(rule["allOf"], `${path}.allOf`));
11155
+ if (rule["anyOf"] !== void 0) problems.push(...checkRuleList(rule["anyOf"], `${path}.anyOf`));
11156
+ if (rule["not"] !== void 0) problems.push(...checkRule(rule["not"], `${path}.not`));
11157
+ return problems;
11158
+ }
11159
+ function findAuthoritiesRuleProblems(authorities) {
11160
+ if (typeof authorities === "string") {
11161
+ return authorities.length > 0 ? [] : ["the profile name is empty"];
11162
+ }
11163
+ if (Array.isArray(authorities)) {
11164
+ if (authorities.length === 0) return ["the list of profiles is empty"];
11165
+ return authorities.every(isNonEmptyString) ? [] : ["a list of profiles must contain only non-empty profile names"];
11166
+ }
11167
+ if (!isPlainObject(authorities)) {
11168
+ return ["must be a profile name, a list of profile names, or a rule object"];
11169
+ }
11170
+ return checkRule(authorities, "");
11171
+ }
11172
+ function findAuthoritiesProfileProblems(name, policy) {
11173
+ return checkRule(policy, "").map((problem) => `profile "${name}": ${problem.replace(/^\./, "")}`);
11174
+ }
11175
+
10794
11176
  // libs/auth/src/authorities/authorities.engine.ts
10795
11177
  function mergeResult(base, ...others) {
10796
11178
  const evaluatedPolicies = [...base.evaluatedPolicies];
@@ -10799,8 +11181,12 @@ function mergeResult(base, ...others) {
10799
11181
  }
10800
11182
  return { ...base, evaluatedPolicies };
10801
11183
  }
10802
- function granted(policies) {
10803
- return { granted: true, evaluatedPolicies: policies };
11184
+ function invalidRule(problems, policies = []) {
11185
+ return {
11186
+ granted: false,
11187
+ deniedBy: `invalid authorities rule: ${problems.join("; ")}`,
11188
+ evaluatedPolicies: policies
11189
+ };
10804
11190
  }
10805
11191
  var AuthoritiesEngine = class {
10806
11192
  constructor(profiles, evaluators) {
@@ -10809,10 +11195,36 @@ var AuthoritiesEngine = class {
10809
11195
  }
10810
11196
  profiles;
10811
11197
  evaluators;
11198
+ /** Problems found per rule object, so a rule is validated once however often it is evaluated. */
11199
+ ruleProblems = /* @__PURE__ */ new WeakMap();
11200
+ /**
11201
+ * Problems with an `authorities` value declared on an entry (see {@link findAuthoritiesRuleProblems}),
11202
+ * and each profile name it uses that no registered profile has. The server reports them when it
11203
+ * starts; {@link evaluate} denies such a value (an unknown profile as `profile '…' is not registered`).
11204
+ */
11205
+ findRuleProblems(authorities) {
11206
+ const problems = findAuthoritiesRuleProblems(authorities);
11207
+ if (problems.length > 0) return problems;
11208
+ const names = typeof authorities === "string" ? [authorities] : Array.isArray(authorities) ? authorities : [];
11209
+ return names.filter((name) => typeof name === "string" && !this.profiles.has(name)).map((name) => `names an unknown profile "${name}"`);
11210
+ }
11211
+ /**
11212
+ * Problems with every registered profile's rule (see {@link findAuthoritiesRuleProblems}).
11213
+ * The server reports them when it starts.
11214
+ */
11215
+ findProfileProblems() {
11216
+ return Object.entries(this.profiles.getAll()).flatMap(
11217
+ ([name, policy]) => findAuthoritiesProfileProblems(name, policy)
11218
+ );
11219
+ }
10812
11220
  /**
10813
11221
  * Evaluate an AuthoritiesMetadata value (string, string[], or policy object).
11222
+ *
11223
+ * A value that is malformed or checks nothing (see {@link findAuthoritiesRuleProblems}) is denied.
10814
11224
  */
10815
11225
  async evaluate(authorities, ctx) {
11226
+ const problems = this.problemsOf(authorities, findAuthoritiesRuleProblems);
11227
+ if (problems.length > 0) return invalidRule(problems);
10816
11228
  if (typeof authorities === "string") {
10817
11229
  return this.evaluateProfile(authorities, ctx);
10818
11230
  }
@@ -10833,6 +11245,8 @@ var AuthoritiesEngine = class {
10833
11245
  evaluatedPolicies: [`profile:${name}`]
10834
11246
  };
10835
11247
  }
11248
+ const problems = this.problemsOf(policy, (rule) => findAuthoritiesProfileProblems(name, rule));
11249
+ if (problems.length > 0) return invalidRule(problems, [`profile:${name}`]);
10836
11250
  const result = await this.evaluatePolicy(policy, ctx);
10837
11251
  if (!result.granted) {
10838
11252
  return {
@@ -10843,6 +11257,16 @@ var AuthoritiesEngine = class {
10843
11257
  }
10844
11258
  return mergeResult(result, { granted: true, evaluatedPolicies: [`profile:${name}`] });
10845
11259
  }
11260
+ /** Validate a rule, reusing the result for a rule object seen before. */
11261
+ problemsOf(rule, find) {
11262
+ if (typeof rule !== "object" || rule === null) return find(rule);
11263
+ let problems = this.ruleProblems.get(rule);
11264
+ if (!problems) {
11265
+ problems = find(rule);
11266
+ this.ruleProblems.set(rule, problems);
11267
+ }
11268
+ return problems;
11269
+ }
10846
11270
  /**
10847
11271
  * Evaluate an array of profile names (AND semantics).
10848
11272
  */
@@ -10891,7 +11315,7 @@ var AuthoritiesEngine = class {
10891
11315
  results.push(await this.evaluateNot(policy.not, ctx));
10892
11316
  }
10893
11317
  if (results.length === 0) {
10894
- return granted([]);
11318
+ return invalidRule(["checks nothing"]);
10895
11319
  }
10896
11320
  return this.combineResults(results, operator);
10897
11321
  }
@@ -10942,14 +11366,15 @@ var AuthoritiesEngine = class {
10942
11366
  }
10943
11367
  /**
10944
11368
  * Evaluate async guard functions in sequence.
10945
- * Each guard returns true (granted) or a string (denial message).
11369
+ * Only `true` grants. A string is the denial message; anything else (`false`, but also
11370
+ * `undefined` from a guard that forgot to return, `null`, `0` or an object) denies.
10946
11371
  */
10947
11372
  async evaluateGuards(guards, ctx) {
10948
11373
  for (let i = 0; i < guards.length; i++) {
10949
11374
  const guard = guards[i];
10950
11375
  const result = await guard(ctx);
10951
- if (result === false || typeof result === "string") {
10952
- const denialMessage = typeof result === "string" ? result : `guard[${i}] denied`;
11376
+ if (result !== true) {
11377
+ const denialMessage = typeof result === "string" ? result : result === false ? `guard[${i}] denied` : `guard[${i}] did not return true (returned ${result === null ? "null" : typeof result})`;
10953
11378
  return {
10954
11379
  granted: false,
10955
11380
  deniedBy: `guards[${i}]: ${denialMessage}`,
@@ -11007,6 +11432,13 @@ var AuthoritiesEngine = class {
11007
11432
  evaluatedPolicies: ["not", ...result.evaluatedPolicies]
11008
11433
  };
11009
11434
  }
11435
+ /**
11436
+ * The policy a profile name stands for when `evaluate()` is given that name, or `undefined`
11437
+ * when no such profile is registered. Lets callers inspect a named rule without evaluating it.
11438
+ */
11439
+ resolveProfile(name) {
11440
+ return this.profiles.resolve(name);
11441
+ }
11010
11442
  };
11011
11443
 
11012
11444
  // libs/auth/src/authorities/authorities.registry.ts
@@ -11252,6 +11684,7 @@ var FetchCredentialMiddleware = class {
11252
11684
  AuthoritiesProfileRegistry,
11253
11685
  AuthorityDeniedError,
11254
11686
  AuthorizationBase,
11687
+ BUILT_IN_AUTH_PAGE_CSP,
11255
11688
  CDN,
11256
11689
  CREDENTIALS_ACCESSOR,
11257
11690
  CimdClientIdMismatchError,
@@ -11266,6 +11699,7 @@ var FetchCredentialMiddleware = class {
11266
11699
  CredentialProviderAlreadyRegisteredError,
11267
11700
  CredentialStorageError,
11268
11701
  CredentialsAccessorImpl,
11702
+ DEFAULT_ALLOWED_SCOPES,
11269
11703
  DEFAULT_INCREMENTAL_TICKET_TTL_MS,
11270
11704
  DEFAULT_RESUME_TTL_MS,
11271
11705
  DEFAULT_THEME,
@@ -11304,6 +11738,9 @@ var FetchCredentialMiddleware = class {
11304
11738
  RedisAuthorizationStore,
11305
11739
  RedisSessionStore,
11306
11740
  SECURE_STORE_ACCESSOR,
11741
+ SIGNIN_BINDING_COOKIE_PREFIX,
11742
+ SIGNIN_BINDING_MAX_AGE_SECONDS,
11743
+ SIGNIN_BINDING_SECURE_COOKIE_PREFIX,
11307
11744
  ScopeDeniedError,
11308
11745
  SecureStoreAccessorImpl,
11309
11746
  SessionCredentialVault,
@@ -11331,6 +11768,7 @@ var FetchCredentialMiddleware = class {
11331
11768
  abacConditionSchema,
11332
11769
  abacOperatorSchema,
11333
11770
  abacPolicySchema,
11771
+ advertisedScopes,
11334
11772
  aiPlatformTypeSchema,
11335
11773
  allowsPublicAccess,
11336
11774
  apiKeyCredentialSchema,
@@ -11379,6 +11817,7 @@ var FetchCredentialMiddleware = class {
11379
11817
  buildToolConsentPage,
11380
11818
  buildUnauthorizedHeader,
11381
11819
  buildWwwAuthenticate,
11820
+ builtInAuthPageHeaders,
11382
11821
  centeredCardLayout,
11383
11822
  checkSsrfProtection,
11384
11823
  cimdCacheConfigSchema,
@@ -11398,6 +11837,7 @@ var FetchCredentialMiddleware = class {
11398
11837
  createLayout,
11399
11838
  createSecureStore,
11400
11839
  createSessionCredentialVault,
11840
+ createSigninBinding,
11401
11841
  createTokenStorageAdapter,
11402
11842
  credentialProviderConfigSchema,
11403
11843
  credentialSchema,
@@ -11430,6 +11870,8 @@ var FetchCredentialMiddleware = class {
11430
11870
  federatedLoginStateSchema,
11431
11871
  federatedProviderItemSchema,
11432
11872
  federatedSelectionSchema,
11873
+ findAuthoritiesProfileProblems,
11874
+ findAuthoritiesRuleProblems,
11433
11875
  flatRemoteProviderFields,
11434
11876
  fromSessionRecord,
11435
11877
  generateAuthorizationCode,
@@ -11441,6 +11883,7 @@ var FetchCredentialMiddleware = class {
11441
11883
  getProviderApps,
11442
11884
  getProviderScopes,
11443
11885
  getTokenSignatureFingerprint,
11886
+ grantScopes,
11444
11887
  hasOnlyLocalhostRedirectUris,
11445
11888
  headerApplier,
11446
11889
  hkdfSha256,
@@ -11450,6 +11893,7 @@ var FetchCredentialMiddleware = class {
11450
11893
  isFrontMcpCredentials,
11451
11894
  isJwt,
11452
11895
  isLocalMode,
11896
+ isLoopbackRedirectUri,
11453
11897
  isOrchestratedLocal,
11454
11898
  isOrchestratedMode,
11455
11899
  isOrchestratedRemote,
@@ -11479,6 +11923,7 @@ var FetchCredentialMiddleware = class {
11479
11923
  noopLogger,
11480
11924
  normalizeIssuer,
11481
11925
  oauthCredentialSchema,
11926
+ openPendingLogin,
11482
11927
  orchestratedLocalSchema,
11483
11928
  orchestratedRemoteSchema,
11484
11929
  parseAuthOptions,
@@ -11506,6 +11951,7 @@ var FetchCredentialMiddleware = class {
11506
11951
  resolveRequiredScopes,
11507
11952
  resourceIdRefSchema,
11508
11953
  safeDecrypt,
11954
+ sealPendingLogin,
11509
11955
  secureStoreConfigSchema,
11510
11956
  secureStoreCustomBackendSchema,
11511
11957
  secureStoreEncryptionSchema,
@@ -11516,6 +11962,8 @@ var FetchCredentialMiddleware = class {
11516
11962
  signCredentialResumeToken,
11517
11963
  signIncrementalAuthTicket,
11518
11964
  signSession,
11965
+ signinBindingCookieName,
11966
+ signinBindingMatches,
11519
11967
  skippedAppBehaviorSchema,
11520
11968
  sseTransportStateSchema,
11521
11969
  sshKeyCredentialSchema,
@@ -11548,5 +11996,6 @@ var FetchCredentialMiddleware = class {
11548
11996
  verifyOrParseSession,
11549
11997
  verifyPkce,
11550
11998
  verifySession,
11551
- wideLayout
11999
+ wideLayout,
12000
+ withoutSecretFieldValues
11552
12001
  });