@frontmcp/sdk 1.7.1 → 1.7.2

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 (76) hide show
  1. package/auth/flows/oauth.authorize.flow.d.ts +30 -0
  2. package/auth/flows/oauth.authorize.flow.d.ts.map +1 -1
  3. package/auth/flows/oauth.callback.flow.d.ts.map +1 -1
  4. package/auth/instances/instance.local-primary-auth.d.ts +35 -0
  5. package/auth/instances/instance.local-primary-auth.d.ts.map +1 -1
  6. package/builtin/config/providers/config-loader.d.ts +5 -0
  7. package/builtin/config/providers/config-loader.d.ts.map +1 -1
  8. package/common/metadata/front-mcp.metadata.d.ts +26 -14
  9. package/common/metadata/front-mcp.metadata.d.ts.map +1 -1
  10. package/common/types/options/transport/schema.d.ts +2 -0
  11. package/common/types/options/transport/schema.d.ts.map +1 -1
  12. package/common/utils/index.d.ts +1 -0
  13. package/common/utils/index.d.ts.map +1 -1
  14. package/common/utils/principal.utils.d.ts +29 -0
  15. package/common/utils/principal.utils.d.ts.map +1 -0
  16. package/direct/llm-platform.d.ts +2 -2
  17. package/direct/llm-platform.d.ts.map +1 -1
  18. package/errors/index.d.ts +1 -0
  19. package/errors/index.d.ts.map +1 -1
  20. package/errors/job.errors.d.ts +26 -0
  21. package/errors/job.errors.d.ts.map +1 -0
  22. package/esm/index.mjs +899 -185
  23. package/front-mcp/front-mcp.providers.d.ts +4 -4
  24. package/index.js +1012 -298
  25. package/job/execution/job-execution.manager.d.ts +15 -0
  26. package/job/execution/job-execution.manager.d.ts.map +1 -1
  27. package/job/job-permission.guard.d.ts +23 -4
  28. package/job/job-permission.guard.d.ts.map +1 -1
  29. package/job/job-scope.helper.d.ts +12 -0
  30. package/job/job-scope.helper.d.ts.map +1 -1
  31. package/job/store/job-state.interface.d.ts +6 -0
  32. package/job/store/job-state.interface.d.ts.map +1 -1
  33. package/job/tools/execute-job.tool.d.ts.map +1 -1
  34. package/job/tools/get-job-status.tool.d.ts +12 -0
  35. package/job/tools/get-job-status.tool.d.ts.map +1 -1
  36. package/job/tools/list-jobs.tool.d.ts +6 -0
  37. package/job/tools/list-jobs.tool.d.ts.map +1 -1
  38. package/job/tools/register-job.tool.d.ts +1 -0
  39. package/job/tools/register-job.tool.d.ts.map +1 -1
  40. package/job/tools/remove-job.tool.d.ts.map +1 -1
  41. package/package.json +10 -10
  42. package/scope/scope.utils.d.ts.map +1 -1
  43. package/server/adapters/express.host.adapter.d.ts +32 -0
  44. package/server/adapters/express.host.adapter.d.ts.map +1 -1
  45. package/server/middleware/host-validation.middleware.d.ts +8 -4
  46. package/server/middleware/host-validation.middleware.d.ts.map +1 -1
  47. package/server/security/host-validation.d.ts +66 -0
  48. package/server/security/host-validation.d.ts.map +1 -0
  49. package/server/security/resolve-allowed-hosts.d.ts +63 -0
  50. package/server/security/resolve-allowed-hosts.d.ts.map +1 -0
  51. package/server/server.instance.d.ts.map +1 -1
  52. package/server/server.types.d.ts +11 -6
  53. package/server/server.types.d.ts.map +1 -1
  54. package/tool/flows/call-tool.flow.d.ts +17 -1
  55. package/tool/flows/call-tool.flow.d.ts.map +1 -1
  56. package/transport/adapters/base-sse-transport.d.ts +7 -0
  57. package/transport/adapters/base-sse-transport.d.ts.map +1 -1
  58. package/transport/adapters/transport.local.adapter.d.ts.map +1 -1
  59. package/transport/adapters/transport.streamable-http.adapter.d.ts.map +1 -1
  60. package/transport/event-stores/index.d.ts +2 -0
  61. package/transport/event-stores/index.d.ts.map +1 -1
  62. package/transport/event-stores/memory.event-store.d.ts +9 -2
  63. package/transport/event-stores/memory.event-store.d.ts.map +1 -1
  64. package/transport/event-stores/redis.event-store.d.ts +24 -3
  65. package/transport/event-stores/redis.event-store.d.ts.map +1 -1
  66. package/transport/event-stores/session-scoped.event-store.d.ts +17 -0
  67. package/transport/event-stores/session-scoped.event-store.d.ts.map +1 -0
  68. package/transport/web-fetch-handler.d.ts.map +1 -1
  69. package/workflow/tools/execute-workflow.tool.d.ts.map +1 -1
  70. package/workflow/tools/get-workflow-status.tool.d.ts +12 -0
  71. package/workflow/tools/get-workflow-status.tool.d.ts.map +1 -1
  72. package/workflow/tools/list-workflows.tool.d.ts +1 -0
  73. package/workflow/tools/list-workflows.tool.d.ts.map +1 -1
  74. package/workflow/tools/register-workflow.tool.d.ts +1 -0
  75. package/workflow/tools/register-workflow.tool.d.ts.map +1 -1
  76. package/workflow/tools/remove-workflow.tool.d.ts.map +1 -1
package/index.js CHANGED
@@ -2309,7 +2309,10 @@ var init_schema7 = __esm({
2309
2309
  });
2310
2310
  eventStoreConfigSchema = import_lazy_zod12.z.object({
2311
2311
  enabled: import_lazy_zod12.z.boolean().default(false),
2312
- provider: import_lazy_zod12.z.enum(["memory", "redis"]).optional().default("memory"),
2312
+ // 'sqlite' is supported by createEventStore and the TS interface but was
2313
+ // missing from this enum, so a valid `provider: 'sqlite'` config failed
2314
+ // validation.
2315
+ provider: import_lazy_zod12.z.enum(["memory", "redis", "sqlite"]).optional().default("memory"),
2313
2316
  maxEvents: import_lazy_zod12.z.number().int().positive().optional().default(1e4),
2314
2317
  ttlMs: import_lazy_zod12.z.number().int().positive().optional().default(3e5),
2315
2318
  redis: redisOptionsSchema.optional()
@@ -4038,7 +4041,7 @@ function formatToolsForPlatform(tools, platform) {
4038
4041
  })
4039
4042
  );
4040
4043
  case "vercel-ai": {
4041
- const result = {};
4044
+ const result = /* @__PURE__ */ Object.create(null);
4042
4045
  for (const tool of tools) {
4043
4046
  result[tool.name] = {
4044
4047
  description: tool.description ?? `Execute ${tool.name}`,
@@ -5576,6 +5579,32 @@ var init_sdk_errors = __esm({
5576
5579
  }
5577
5580
  });
5578
5581
 
5582
+ // libs/sdk/src/errors/job.errors.ts
5583
+ var JobNotAuthorizedError, DynamicJobRegistrationDisabledError;
5584
+ var init_job_errors = __esm({
5585
+ "libs/sdk/src/errors/job.errors.ts"() {
5586
+ "use strict";
5587
+ init_mcp_error();
5588
+ JobNotAuthorizedError = class extends PublicMcpError {
5589
+ /** The requested name, for internal logs only — never surfaced publicly. */
5590
+ requestedName;
5591
+ constructor(requestedName) {
5592
+ super(`Job or workflow "${requestedName}" not found or not permitted`, "JOB_NOT_AUTHORIZED", 403);
5593
+ this.requestedName = requestedName;
5594
+ }
5595
+ };
5596
+ DynamicJobRegistrationDisabledError = class extends PublicMcpError {
5597
+ constructor(kind = "job") {
5598
+ super(
5599
+ `Dynamic ${kind} registration is disabled. Enable it with jobs: { allowDynamicRegistration: true }.`,
5600
+ "DYNAMIC_REGISTRATION_DISABLED",
5601
+ 403
5602
+ );
5603
+ }
5604
+ };
5605
+ }
5606
+ });
5607
+
5579
5608
  // libs/sdk/src/errors/index.ts
5580
5609
  var errors_exports = {};
5581
5610
  __export(errors_exports, {
@@ -5601,6 +5630,7 @@ __export(errors_exports, {
5601
5630
  DependencyNotFoundError: () => DependencyNotFoundError,
5602
5631
  DynamicAdapterNameError: () => DynamicAdapterNameError,
5603
5632
  DynamicJobDirectExecutionError: () => DynamicJobDirectExecutionError,
5633
+ DynamicJobRegistrationDisabledError: () => DynamicJobRegistrationDisabledError,
5604
5634
  ElicitationDisabledError: () => ElicitationDisabledError,
5605
5635
  ElicitationEncryptionError: () => ElicitationEncryptionError,
5606
5636
  ElicitationFallbackRequired: () => ElicitationFallbackRequired,
@@ -5649,6 +5679,7 @@ __export(errors_exports, {
5649
5679
  InvalidUseFactoryError: () => InvalidUseFactoryError,
5650
5680
  InvalidUseValueError: () => InvalidUseValueError,
5651
5681
  InvokeStateMissingKeyError: () => InvokeStateMissingKeyError,
5682
+ JobNotAuthorizedError: () => JobNotAuthorizedError,
5652
5683
  MCP_ERROR_CODES: () => MCP_ERROR_CODES,
5653
5684
  McpError: () => McpError,
5654
5685
  MethodNotImplementedError: () => MethodNotImplementedError,
@@ -5771,6 +5802,7 @@ var init_errors = __esm({
5771
5802
  init_workflow_errors();
5772
5803
  init_esm_errors();
5773
5804
  init_sdk_errors();
5805
+ init_job_errors();
5774
5806
  }
5775
5807
  });
5776
5808
 
@@ -7093,6 +7125,10 @@ var require_dist = __commonJS({
7093
7125
  }
7094
7126
  return id;
7095
7127
  }
7128
+ async getStreamIdForEventId(eventId) {
7129
+ const row = this.prepared().getStreamId.get(eventId);
7130
+ return row?.stream_id;
7131
+ }
7096
7132
  async replayEventsAfter(lastEventId, { send }) {
7097
7133
  const stmts = this.prepared();
7098
7134
  const streamRow = stmts.getStreamId.get(lastEventId);
@@ -14015,22 +14051,38 @@ async function loadYamlConfig(basePath, configPath) {
14015
14051
  }
14016
14052
  return {};
14017
14053
  }
14054
+ function stripUnsafeKeys(value) {
14055
+ if (Array.isArray(value)) return value.map(stripUnsafeKeys);
14056
+ if (!isPlainObject(value)) return value;
14057
+ const clean = {};
14058
+ for (const [key, nested] of Object.entries(value)) {
14059
+ if (UNSAFE_MERGE_KEYS.has(key)) continue;
14060
+ clean[key] = stripUnsafeKeys(nested);
14061
+ }
14062
+ return clean;
14063
+ }
14064
+ function isPlainObject(value) {
14065
+ if (!value || typeof value !== "object") return false;
14066
+ const proto = Object.getPrototypeOf(value);
14067
+ return proto === Object.prototype || proto === null;
14068
+ }
14018
14069
  function deepMerge(target, source) {
14019
14070
  const result = { ...target };
14020
14071
  for (const key in source) {
14072
+ if (UNSAFE_MERGE_KEYS.has(key)) continue;
14021
14073
  const sourceVal = source[key];
14022
14074
  const targetVal = result[key];
14023
14075
  if (sourceVal !== void 0 && sourceVal !== null) {
14024
14076
  if (typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && targetVal !== null) {
14025
14077
  result[key] = deepMerge(targetVal, sourceVal);
14026
14078
  } else {
14027
- result[key] = sourceVal;
14079
+ result[key] = stripUnsafeKeys(sourceVal);
14028
14080
  }
14029
14081
  }
14030
14082
  }
14031
14083
  return result;
14032
14084
  }
14033
- var yaml2, import_utils32;
14085
+ var yaml2, import_utils32, UNSAFE_MERGE_KEYS;
14034
14086
  var init_config_loader = __esm({
14035
14087
  "libs/sdk/src/builtin/config/providers/config-loader.ts"() {
14036
14088
  "use strict";
@@ -14038,6 +14090,7 @@ var init_config_loader = __esm({
14038
14090
  import_utils32 = require("@frontmcp/utils");
14039
14091
  init_config_service();
14040
14092
  init_env_loader();
14093
+ UNSAFE_MERGE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
14041
14094
  }
14042
14095
  });
14043
14096
 
@@ -20704,10 +20757,11 @@ var init_tool_credentials_gate = __esm({
20704
20757
  });
20705
20758
 
20706
20759
  // libs/sdk/src/tool/flows/call-tool.flow.ts
20707
- var import_guard4, import_lazy_zod56, import_protocol38, import_adapters4, import_utils45, inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, CallToolFlow;
20760
+ var import_auth17, import_guard4, import_lazy_zod56, import_protocol38, import_adapters4, import_utils45, inputSchema13, outputSchema12, stateSchema12, plan12, name12, Stage12, CallToolFlow;
20708
20761
  var init_call_tool_flow = __esm({
20709
20762
  "libs/sdk/src/tool/flows/call-tool.flow.ts"() {
20710
20763
  "use strict";
20764
+ import_auth17 = require("@frontmcp/auth");
20711
20765
  import_guard4 = require("@frontmcp/guard");
20712
20766
  import_lazy_zod56 = require("@frontmcp/lazy-zod");
20713
20767
  import_protocol38 = require("@frontmcp/protocol");
@@ -21080,7 +21134,7 @@ var init_call_tool_flow = __esm({
21080
21134
  this.logger.verbose(`checkToolAuthorization: using anonymous for ${appId}`);
21081
21135
  return;
21082
21136
  }
21083
- const authUrl = this.buildProgressiveAuthUrl(appId, tool?.fullName || "");
21137
+ const authUrl = this.buildProgressiveAuthUrl(appId, tool?.fullName || "", authInfo, claimAuthorizedApps);
21084
21138
  this.logger.info(`checkToolAuthorization: authorization required for app "${appId}"`);
21085
21139
  throw new AuthorizationRequiredError({
21086
21140
  appId,
@@ -21092,11 +21146,51 @@ var init_call_tool_flow = __esm({
21092
21146
  this.logger.verbose("checkToolAuthorization:done");
21093
21147
  }
21094
21148
  /**
21095
- * Build the URL for progressive/incremental authorization
21149
+ * Build the URL for progressive/incremental authorization.
21150
+ *
21151
+ * This is the ONE point in the request lifecycle where the caller's identity
21152
+ * is both known and verified, so it is where the incremental-authorization
21153
+ * ticket is minted (GHSA-2c4g-9c8x-6m8g). The ticket is what later authorizes
21154
+ * `/oauth/authorize` to skip the login step; `app`/`tool` are carried purely
21155
+ * for display and are re-read from the signed payload server-side.
21156
+ *
21157
+ * When no verified subject or signing secret is available the URL is emitted
21158
+ * WITHOUT a ticket — the user then completes an ordinary login, which is the
21159
+ * correct fail-closed outcome rather than a credential-free shortcut.
21096
21160
  */
21097
- buildProgressiveAuthUrl(appId, toolId) {
21161
+ buildProgressiveAuthUrl(appId, toolId, authInfo, priorAppIds) {
21098
21162
  const baseUrl = this.scope.fullPath || "";
21099
- return `${baseUrl}/oauth/authorize?app=${encodeURIComponent(appId)}&tool=${encodeURIComponent(toolId)}`;
21163
+ const url = new URL(`${baseUrl}/oauth/authorize`, "http://placeholder.invalid");
21164
+ url.searchParams.set("app", appId);
21165
+ url.searchParams.set("tool", toolId);
21166
+ const ticket = this.mintIncrementalTicket(appId, toolId, authInfo, priorAppIds);
21167
+ if (ticket) {
21168
+ url.searchParams.set("ticket", ticket);
21169
+ } else {
21170
+ this.logger.verbose("buildProgressiveAuthUrl: no incremental ticket minted \u2014 link requires a full login");
21171
+ }
21172
+ return baseUrl.startsWith("http") ? url.toString() : `${url.pathname}${url.search}`;
21173
+ }
21174
+ /**
21175
+ * Mint a single-use, short-lived incremental-authorization ticket for the
21176
+ * verified caller. Returns undefined when the scope has no local signing
21177
+ * secret or the request has no verified subject.
21178
+ */
21179
+ mintIncrementalTicket(appId, toolId, authInfo, priorAppIds) {
21180
+ const extraUser = authInfo?.extra?.user;
21181
+ const sub = extraUser?.sub ?? authInfo?.user?.sub;
21182
+ if (!sub) return void 0;
21183
+ const secret = this.scope.auth?.signingSecret;
21184
+ if (!secret) return void 0;
21185
+ return (0, import_auth17.signIncrementalAuthTicket)(
21186
+ {
21187
+ sub,
21188
+ appId,
21189
+ toolId,
21190
+ ...priorAppIds && priorAppIds.size > 0 ? { priorAppIds: [...priorAppIds] } : {}
21191
+ },
21192
+ secret
21193
+ );
21100
21194
  }
21101
21195
  async checkEntryAuthorities() {
21102
21196
  this.logger.verbose("checkEntryAuthorities:start");
@@ -35577,25 +35671,25 @@ var init_auth_utils = __esm({
35577
35671
  });
35578
35672
 
35579
35673
  // libs/sdk/src/auth/authorization/orchestrated.context-extension.ts
35580
- var import_auth17, orchestratedAuthContextExtension;
35674
+ var import_auth18, orchestratedAuthContextExtension;
35581
35675
  var init_orchestrated_context_extension = __esm({
35582
35676
  "libs/sdk/src/auth/authorization/orchestrated.context-extension.ts"() {
35583
35677
  "use strict";
35584
- import_auth17 = require("@frontmcp/auth");
35678
+ import_auth18 = require("@frontmcp/auth");
35585
35679
  orchestratedAuthContextExtension = {
35586
35680
  property: "orchestration",
35587
- token: import_auth17.ORCHESTRATED_AUTH_ACCESSOR,
35681
+ token: import_auth18.ORCHESTRATED_AUTH_ACCESSOR,
35588
35682
  errorMessage: "Orchestrated authorization not available. Ensure your server is configured with orchestrated mode and the user has completed authentication."
35589
35683
  };
35590
35684
  }
35591
35685
  });
35592
35686
 
35593
35687
  // libs/sdk/src/auth/cimd/index.ts
35594
- var import_auth18;
35688
+ var import_auth19;
35595
35689
  var init_cimd = __esm({
35596
35690
  "libs/sdk/src/auth/cimd/index.ts"() {
35597
35691
  "use strict";
35598
- import_auth18 = require("@frontmcp/auth");
35692
+ import_auth19 = require("@frontmcp/auth");
35599
35693
  }
35600
35694
  });
35601
35695
 
@@ -35617,9 +35711,9 @@ function createCredentialsProviders(opts) {
35617
35711
  name: "SessionCredentialVault"
35618
35712
  };
35619
35713
  const accessorProvider = {
35620
- provide: import_auth19.CREDENTIALS_ACCESSOR,
35714
+ provide: import_auth20.CREDENTIALS_ACCESSOR,
35621
35715
  inject: () => [FRONTMCP_CONTEXT, FrontMcpLogger],
35622
- useFactory: (ctx, logger) => new import_auth19.CredentialsAccessorImpl({
35716
+ useFactory: (ctx, logger) => new import_auth20.CredentialsAccessorImpl({
35623
35717
  vault,
35624
35718
  resolveSub: () => resolveRequestSub(ctx),
35625
35719
  signingSecret,
@@ -35632,11 +35726,11 @@ function createCredentialsProviders(opts) {
35632
35726
  };
35633
35727
  return [vaultProvider, accessorProvider];
35634
35728
  }
35635
- var import_auth19, import_di31, SESSION_CREDENTIAL_VAULT;
35729
+ var import_auth20, import_di31, SESSION_CREDENTIAL_VAULT;
35636
35730
  var init_credentials_providers = __esm({
35637
35731
  "libs/sdk/src/auth/credentials/credentials.providers.ts"() {
35638
35732
  "use strict";
35639
- import_auth19 = require("@frontmcp/auth");
35733
+ import_auth20 = require("@frontmcp/auth");
35640
35734
  import_di31 = require("@frontmcp/di");
35641
35735
  init_common();
35642
35736
  init_frontmcp_context_provider();
@@ -35647,14 +35741,14 @@ var init_credentials_providers = __esm({
35647
35741
  });
35648
35742
 
35649
35743
  // libs/sdk/src/auth/credentials/credentials.context-extension.ts
35650
- var import_auth20, credentialsContextExtension;
35744
+ var import_auth21, credentialsContextExtension;
35651
35745
  var init_credentials_context_extension = __esm({
35652
35746
  "libs/sdk/src/auth/credentials/credentials.context-extension.ts"() {
35653
35747
  "use strict";
35654
- import_auth20 = require("@frontmcp/auth");
35748
+ import_auth21 = require("@frontmcp/auth");
35655
35749
  credentialsContextExtension = {
35656
35750
  property: "credentials",
35657
- token: import_auth20.CREDENTIALS_ACCESSOR,
35751
+ token: import_auth21.CREDENTIALS_ACCESSOR,
35658
35752
  errorMessage: "Per-session credentials are not available. Ensure the server uses auth mode 'local' (or 'remote'); credentials are persisted by a local authenticate() verifier."
35659
35753
  };
35660
35754
  }
@@ -35823,11 +35917,11 @@ function getConfiguredProviders(auth) {
35823
35917
  function getConfiguredProviderIds(auth) {
35824
35918
  return getConfiguredProviders(auth).map((p) => p.id);
35825
35919
  }
35826
- var import_auth21, import_lazy_zod64, import_utils66, pkceChallengeSchema, codeChallengeMethodSchema, responseTypeSchema, safeRedirectUriSchema, oauthAuthorizeRequestSchema, anonymousAuthorizeRequestSchema, inputSchema16, stateSchema15, outputSchema15, plan15, name15, Stage15, OauthAuthorizeFlow;
35920
+ var import_auth22, import_lazy_zod64, import_utils66, pkceChallengeSchema, codeChallengeMethodSchema, responseTypeSchema, safeRedirectUriSchema, oauthAuthorizeRequestSchema, anonymousAuthorizeRequestSchema, inputSchema16, stateSchema15, outputSchema15, plan15, name15, Stage15, OauthAuthorizeFlow;
35827
35921
  var init_oauth_authorize_flow = __esm({
35828
35922
  "libs/sdk/src/auth/flows/oauth.authorize.flow.ts"() {
35829
35923
  "use strict";
35830
- import_auth21 = require("@frontmcp/auth");
35924
+ import_auth22 = require("@frontmcp/auth");
35831
35925
  import_lazy_zod64 = require("@frontmcp/lazy-zod");
35832
35926
  import_utils66 = require("@frontmcp/utils");
35833
35927
  init_common();
@@ -35885,6 +35979,13 @@ var init_oauth_authorize_flow = __esm({
35885
35979
  targetAppId: import_lazy_zod64.z.string().optional().describe("Target app ID for incremental authorization"),
35886
35980
  targetToolId: import_lazy_zod64.z.string().optional().describe("Target tool ID that triggered the incremental auth"),
35887
35981
  existingSessionId: import_lazy_zod64.z.string().optional().describe("Existing session ID for incremental auth"),
35982
+ /**
35983
+ * The VERIFIED incremental-authorization ticket (GHSA-2c4g-9c8x-6m8g). Set
35984
+ * only when `?ticket=` carried a valid, unexpired server signature. Its `sub`
35985
+ * is the proven identity the expanded grant is minted for; `isIncrementalAuth`
35986
+ * is derived from its presence, never from a raw query parameter.
35987
+ */
35988
+ incrementalTicket: import_lazy_zod64.z.custom().optional(),
35888
35989
  /**
35889
35990
  * Apps the client already holds a grant for (the prior `authorized_apps`
35890
35991
  * claim), carried forward on an incremental authorize so the newly-minted
@@ -35898,7 +35999,7 @@ var init_oauth_authorize_flow = __esm({
35898
35999
  requiresConsent: import_lazy_zod64.z.boolean().default(false).describe("Whether consent flow is enabled"),
35899
36000
  // CIMD (Client ID Metadata Documents)
35900
36001
  isCimdClient: import_lazy_zod64.z.boolean().default(false).describe("Whether client_id is a CIMD URL"),
35901
- cimdMetadata: import_auth18.clientMetadataDocumentSchema.optional().describe("CIMD metadata document for the client")
36002
+ cimdMetadata: import_auth19.clientMetadataDocumentSchema.optional().describe("CIMD metadata document for the client")
35902
36003
  });
35903
36004
  outputSchema15 = import_lazy_zod64.z.union([
35904
36005
  HttpRedirectSchema,
@@ -35937,18 +36038,21 @@ var init_oauth_authorize_flow = __esm({
35937
36038
  const { request } = this.rawInput;
35938
36039
  const rawRedirectUri = request.query["redirect_uri"];
35939
36040
  const rawState = request.query["state"];
35940
- const targetAppId = request.query["app"];
35941
- const targetToolId = request.query["tool"];
35942
- const existingSessionId = request.query["session_id"];
35943
- const mode = request.query["mode"];
35944
- const isIncrementalAuth = mode === "incremental" || !!targetAppId;
35945
- const appsParam = request.query["apps"];
36041
+ const incrementalTicket = this.verifyIncrementalTicket(request.query["ticket"]);
36042
+ const isIncrementalAuth = incrementalTicket !== void 0;
36043
+ const targetAppId = incrementalTicket?.appId;
36044
+ const targetToolId = incrementalTicket?.toolId;
35946
36045
  let priorAuthorizedAppIds;
35947
- if (appsParam) {
35948
- const raw = Array.isArray(appsParam) ? appsParam : [appsParam];
35949
- const ids = raw.flatMap((v) => String(v).split(",")).map((s) => s.trim()).filter(Boolean);
35950
- priorAuthorizedAppIds = ids.length > 0 ? Array.from(new Set(ids)) : void 0;
36046
+ if (incrementalTicket) {
36047
+ priorAuthorizedAppIds = incrementalTicket.priorAppIds && incrementalTicket.priorAppIds.length > 0 ? Array.from(new Set(incrementalTicket.priorAppIds)) : void 0;
36048
+ } else {
36049
+ const appsParam = request.query["apps"];
36050
+ if (appsParam) {
36051
+ const ids = (Array.isArray(appsParam) ? appsParam : [appsParam]).flatMap((v) => String(v).split(",")).map((v) => v.trim()).filter(Boolean);
36052
+ priorAuthorizedAppIds = ids.length > 0 ? Array.from(new Set(ids)) : void 0;
36053
+ }
35951
36054
  }
36055
+ const existingSessionId = request.query["session_id"];
35952
36056
  const isDefaultAuthProvider = !metadata.auth;
35953
36057
  let requiresFederatedLogin = false;
35954
36058
  if (metadata.auth && (0, import_auth10.isOrchestratedMode)(metadata.auth)) {
@@ -35974,6 +36078,7 @@ var init_oauth_authorize_flow = __esm({
35974
36078
  targetToolId,
35975
36079
  existingSessionId,
35976
36080
  priorAuthorizedAppIds,
36081
+ incrementalTicket,
35977
36082
  // Federated Login
35978
36083
  requiresFederatedLogin,
35979
36084
  // Consent Flow
@@ -36045,7 +36150,7 @@ var init_oauth_authorize_flow = __esm({
36045
36150
  }
36046
36151
  }
36047
36152
  const { client_id, redirect_uri } = result.data;
36048
- const cimdService = this.get(import_auth18.CimdService);
36153
+ const cimdService = this.get(import_auth19.CimdService);
36049
36154
  const isCimdClientId2 = !!cimdService?.enabled && cimdService.isCimdClientId(client_id);
36050
36155
  const dcrError = this.checkDcrAllowlist(client_id, redirect_uri, isCimdClientId2);
36051
36156
  if (dcrError) {
@@ -36099,6 +36204,28 @@ var init_oauth_authorize_flow = __esm({
36099
36204
  }
36100
36205
  }
36101
36206
  }
36207
+ /**
36208
+ * Verify a client-supplied incremental-authorization ticket.
36209
+ *
36210
+ * Returns the verified payload, or `undefined` when the parameter is absent,
36211
+ * malformed, expired, or not signed by this server. Verification is
36212
+ * side-effect free — the ticket is CLAIMED (single use) later, in
36213
+ * {@link prepareAuthorizationRequest}, so a request that fails OAuth
36214
+ * validation does not burn the user's ticket.
36215
+ */
36216
+ verifyIncrementalTicket(raw) {
36217
+ const token = typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : void 0;
36218
+ if (typeof token !== "string" || token.length === 0) return void 0;
36219
+ const localAuth = this.scope.auth;
36220
+ const secret = localAuth?.signingSecret;
36221
+ if (!secret) return void 0;
36222
+ const payload = (0, import_auth22.verifyIncrementalAuthTicket)(token, secret);
36223
+ if (!payload) {
36224
+ this.logger.warn("Incremental auth ticket failed verification \u2014 treating as an ordinary login");
36225
+ return void 0;
36226
+ }
36227
+ return payload;
36228
+ }
36102
36229
  async checkIfAuthorized() {
36103
36230
  }
36104
36231
  async prepareAuthorizationRequest() {
@@ -36110,7 +36237,8 @@ var init_oauth_authorize_flow = __esm({
36110
36237
  existingSessionId,
36111
36238
  priorAuthorizedAppIds,
36112
36239
  requiresFederatedLogin,
36113
- requiresConsent
36240
+ requiresConsent,
36241
+ incrementalTicket
36114
36242
  } = this.state;
36115
36243
  const { metadata } = this.scope;
36116
36244
  if (!validatedRequest) {
@@ -36123,6 +36251,18 @@ var init_oauth_authorize_flow = __esm({
36123
36251
  }
36124
36252
  const localAuth = auth;
36125
36253
  const store = localAuth.authorizationStore;
36254
+ let effectiveIncremental = isIncrementalAuth;
36255
+ if (incrementalTicket) {
36256
+ const claimed = await localAuth.claimIncrementalTicket(
36257
+ incrementalTicket.jti,
36258
+ Math.max(0, incrementalTicket.exp - Date.now())
36259
+ );
36260
+ if (!claimed) {
36261
+ this.logger.warn("Incremental auth ticket already used \u2014 falling back to an ordinary login");
36262
+ effectiveIncremental = false;
36263
+ this.state.set({ isIncrementalAuth: false, targetAppId: void 0, targetToolId: void 0 });
36264
+ }
36265
+ }
36126
36266
  let federatedLogin;
36127
36267
  if (requiresFederatedLogin) {
36128
36268
  const apps = this.scope.apps.getApps();
@@ -36170,10 +36310,14 @@ var init_oauth_authorize_flow = __esm({
36170
36310
  },
36171
36311
  state: validatedRequest.state,
36172
36312
  resource: validatedRequest.resource,
36173
- // Progressive/Incremental Authorization Fields
36174
- isIncremental: isIncrementalAuth,
36175
- targetAppId,
36176
- targetToolId,
36313
+ // Progressive/Incremental Authorization Fields. `isIncremental` reflects a
36314
+ // CLAIMED, server-signed ticket; `incrementalSub` is the identity that
36315
+ // ticket proved, and is the only subject the callback may mint for when
36316
+ // it skips the credential gate.
36317
+ isIncremental: effectiveIncremental,
36318
+ incrementalSub: effectiveIncremental ? incrementalTicket?.sub : void 0,
36319
+ targetAppId: effectiveIncremental ? targetAppId : void 0,
36320
+ targetToolId: effectiveIncremental ? targetToolId : void 0,
36177
36321
  existingSessionId,
36178
36322
  priorAuthorizedAppIds,
36179
36323
  // Federated Login State
@@ -36183,7 +36327,7 @@ var init_oauth_authorize_flow = __esm({
36183
36327
  });
36184
36328
  await localAuth.authorizationStore.storePendingAuthorization(pendingRecord);
36185
36329
  this.logger.info(
36186
- `Pending authorization created: ${pendingRecord.id}${isIncrementalAuth ? ` (incremental for app: ${targetAppId})` : ""}${requiresFederatedLogin ? " (federated)" : ""}${requiresConsent ? " (consent enabled)" : ""}`
36330
+ `Pending authorization created: ${pendingRecord.id}${effectiveIncremental ? ` (incremental for app: ${targetAppId})` : ""}${requiresFederatedLogin ? " (federated)" : ""}${requiresConsent ? " (consent enabled)" : ""}`
36187
36331
  );
36188
36332
  this.state.set("pendingAuthId", pendingRecord.id);
36189
36333
  }
@@ -36404,7 +36548,7 @@ var init_oauth_authorize_flow = __esm({
36404
36548
  );
36405
36549
  return;
36406
36550
  }
36407
- const session = (0, import_auth21.createFederatedAuthSession)({
36551
+ const session = (0, import_auth22.createFederatedAuthSession)({
36408
36552
  pendingAuthId,
36409
36553
  clientId: validatedRequest.client_id,
36410
36554
  redirectUri: validatedRequest.redirect_uri,
@@ -36419,7 +36563,7 @@ var init_oauth_authorize_flow = __esm({
36419
36563
  const challenge = (0, import_utils66.sha256Base64url)(verifier);
36420
36564
  const pkce = { verifier, challenge, method: "S256" };
36421
36565
  const providerState = `federated:${session.id}:${(0, import_utils66.randomUUID)()}`;
36422
- (0, import_auth21.startNextProvider)(session, pkce, providerState);
36566
+ (0, import_auth22.startNextProvider)(session, pkce, providerState);
36423
36567
  await localAuth.federatedSessionStore.store(session);
36424
36568
  const redirectUrl = await localAuth.buildProviderAuthorizeUrl(providerId, {
36425
36569
  state: providerState,
@@ -36520,7 +36664,7 @@ var init_oauth_authorize_flow = __esm({
36520
36664
  callbackPath,
36521
36665
  fields: login?.fields ?? {}
36522
36666
  };
36523
- return (0, import_auth21.renderLocalLoginPage)(login, ctx);
36667
+ return (0, import_auth22.renderLocalLoginPage)(login, ctx);
36524
36668
  }
36525
36669
  /**
36526
36670
  * Render incremental authorization page for a single app using HTMX templates
@@ -36533,7 +36677,7 @@ var init_oauth_authorize_flow = __esm({
36533
36677
  appName,
36534
36678
  description: appDescription
36535
36679
  };
36536
- return (0, import_auth21.buildIncrementalAuthPage)({
36680
+ return (0, import_auth22.buildIncrementalAuthPage)({
36537
36681
  app,
36538
36682
  toolId: toolId || "unknown tool",
36539
36683
  sessionHint: pendingAuthId,
@@ -36554,7 +36698,7 @@ var init_oauth_authorize_flow = __esm({
36554
36698
  appIds: provider.appIds.filter((id) => id !== "__parent__"),
36555
36699
  isPrimary: provider.isParentProvider
36556
36700
  }));
36557
- return (0, import_auth21.buildFederatedLoginPage)({
36701
+ return (0, import_auth22.buildFederatedLoginPage)({
36558
36702
  providers,
36559
36703
  clientName: clientId,
36560
36704
  pendingAuthId,
@@ -36599,8 +36743,8 @@ var init_oauth_authorize_flow = __esm({
36599
36743
  <div class="error-container">
36600
36744
  <div class="error-icon">\u26A0\uFE0F</div>
36601
36745
  <h1>Authorization Error</h1>
36602
- <p><span class="error-code">${(0, import_auth21.escapeHtml)(error)}</span></p>
36603
- <p>${(0, import_auth21.escapeHtml)(description)}</p>
36746
+ <p><span class="error-code">${(0, import_auth22.escapeHtml)(error)}</span></p>
36747
+ <p>${(0, import_auth22.escapeHtml)(description)}</p>
36604
36748
  </div>
36605
36749
  </body>
36606
36750
  </html>`;
@@ -36649,11 +36793,11 @@ function timingSafeEqualStr3(a, b) {
36649
36793
  }
36650
36794
  return diff === 0;
36651
36795
  }
36652
- var import_auth22, import_lazy_zod65, import_utils67, inputSchema17, stateSchema16, outputSchema16, plan16, name16, Stage16, OauthCallbackFlow;
36796
+ var import_auth23, import_lazy_zod65, import_utils67, inputSchema17, stateSchema16, outputSchema16, plan16, name16, Stage16, OauthCallbackFlow;
36653
36797
  var init_oauth_callback_flow = __esm({
36654
36798
  "libs/sdk/src/auth/flows/oauth.callback.flow.ts"() {
36655
36799
  "use strict";
36656
- import_auth22 = require("@frontmcp/auth");
36800
+ import_auth23 = require("@frontmcp/auth");
36657
36801
  import_lazy_zod65 = require("@frontmcp/lazy-zod");
36658
36802
  import_utils67 = require("@frontmcp/utils");
36659
36803
  init_common();
@@ -36744,8 +36888,6 @@ var init_oauth_callback_flow = __esm({
36744
36888
  const email = request.query["email"];
36745
36889
  const name42 = request.query["name"];
36746
36890
  const loginFields = this.collectLoginFields(request);
36747
- const isIncremental = request.query["incremental"] === "true";
36748
- const targetAppId = request.query["app_id"];
36749
36891
  const isFederated = request.query["federated"] === "true";
36750
36892
  const providersParam = request.query["providers"];
36751
36893
  let selectedProviders;
@@ -36764,8 +36906,6 @@ var init_oauth_callback_flow = __esm({
36764
36906
  pendingAuthId,
36765
36907
  email,
36766
36908
  name: name42,
36767
- isIncremental,
36768
- targetAppId,
36769
36909
  isFederated,
36770
36910
  selectedProviders,
36771
36911
  selectedTools,
@@ -36773,9 +36913,6 @@ var init_oauth_callback_flow = __esm({
36773
36913
  loginFields,
36774
36914
  csrf
36775
36915
  });
36776
- if (isIncremental) {
36777
- this.logger.info(`Incremental auth callback for app: ${targetAppId}`);
36778
- }
36779
36916
  if (isFederated) {
36780
36917
  this.logger.info(`Federated login callback with ${selectedProviders?.length || 0} selected providers`);
36781
36918
  }
@@ -36784,31 +36921,13 @@ var init_oauth_callback_flow = __esm({
36784
36921
  }
36785
36922
  }
36786
36923
  async validatePendingAuth() {
36787
- const {
36788
- pendingAuthId,
36789
- email,
36790
- name: name42,
36791
- isIncremental,
36792
- isFederated,
36793
- selectedProviders,
36794
- selectedTools,
36795
- consentSubmitted,
36796
- loginFields
36797
- } = this.state;
36924
+ const { pendingAuthId, email, name: name42, isFederated, selectedProviders, selectedTools, consentSubmitted, loginFields } = this.state;
36798
36925
  if (!pendingAuthId) {
36799
36926
  this.logger.warn("Missing pending_auth_id in callback");
36800
36927
  this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Missing pending_auth_id parameter"), 400));
36801
36928
  return;
36802
36929
  }
36803
36930
  const localAuth = this.scope.auth;
36804
- const localOptions = localAuth.options;
36805
- const authenticateFn = typeof localOptions.authenticate === "function" ? localOptions.authenticate : void 0;
36806
- const requireEmail = localOptions.requireEmail ?? true;
36807
- if (!authenticateFn && !isIncremental && !email && requireEmail) {
36808
- this.logger.warn("Missing email in callback");
36809
- this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Email is required"), 400));
36810
- return;
36811
- }
36812
36931
  const pendingAuth = await localAuth.authorizationStore.getPendingAuthorization(pendingAuthId);
36813
36932
  if (!pendingAuth) {
36814
36933
  this.logger.warn(`Pending authorization not found or expired: ${pendingAuthId}`);
@@ -36820,6 +36939,20 @@ var init_oauth_callback_flow = __esm({
36820
36939
  );
36821
36940
  return;
36822
36941
  }
36942
+ const isIncremental = pendingAuth.isIncremental === true && typeof pendingAuth.incrementalSub === "string";
36943
+ const targetAppId = isIncremental ? pendingAuth.targetAppId : void 0;
36944
+ this.state.set({ isIncremental, targetAppId });
36945
+ if (isIncremental) {
36946
+ this.logger.info(`Incremental auth callback for app: ${targetAppId}`);
36947
+ }
36948
+ const localOptions = localAuth.options;
36949
+ const authenticateFn = typeof localOptions.authenticate === "function" ? localOptions.authenticate : void 0;
36950
+ const requireEmail = localOptions.requireEmail ?? true;
36951
+ if (!authenticateFn && !isIncremental && !email && requireEmail) {
36952
+ this.logger.warn("Missing email in callback");
36953
+ this.respond(httpRespond.html(this.renderErrorPage("invalid_request", "Email is required"), 400));
36954
+ return;
36955
+ }
36823
36956
  if (pendingAuth.authUiCsrf) {
36824
36957
  const submitted = this.state.csrf;
36825
36958
  if (!submitted || !timingSafeEqualStr3(pendingAuth.authUiCsrf, submitted)) {
@@ -36834,9 +36967,11 @@ var init_oauth_callback_flow = __esm({
36834
36967
  return;
36835
36968
  }
36836
36969
  let userSub;
36837
- if (email) {
36970
+ if (isIncremental) {
36971
+ userSub = pendingAuth.incrementalSub;
36972
+ } else if (email) {
36838
36973
  userSub = this.generateUserSub(email);
36839
- } else if (!requireEmail || isIncremental) {
36974
+ } else if (!requireEmail) {
36840
36975
  const anonymousSubject = localOptions.anonymousSubject ?? "local-operator";
36841
36976
  userSub = this.generateUserSub(anonymousSubject);
36842
36977
  } else {
@@ -37005,10 +37140,13 @@ var init_oauth_callback_flow = __esm({
37005
37140
  originalState: pendingAuth.state,
37006
37141
  resource: pendingAuth.resource,
37007
37142
  userSub,
37008
- // Progressive/Incremental Authorization from pending record
37009
- isIncremental: pendingAuth.isIncremental || isIncremental,
37010
- targetAppId: pendingAuth.targetAppId || this.state.targetAppId,
37011
- targetToolId: pendingAuth.targetToolId,
37143
+ // Progressive/Incremental Authorization — from the pending record ONLY.
37144
+ // `isIncremental` was derived above from the record plus its proven
37145
+ // subject; OR-ing a client-supplied flag in here is what let a caller
37146
+ // override the server's `false` (GHSA-2c4g-9c8x-6m8g).
37147
+ isIncremental,
37148
+ targetAppId,
37149
+ targetToolId: isIncremental ? pendingAuth.targetToolId : void 0,
37012
37150
  existingSessionId: pendingAuth.existingSessionId,
37013
37151
  existingAuthorizationId: pendingAuth.existingAuthorizationId,
37014
37152
  priorAuthorizedAppIds: pendingAuth.priorAuthorizedAppIds,
@@ -37091,7 +37229,7 @@ var init_oauth_callback_flow = __esm({
37091
37229
  this.respond(httpRespond.html(this.renderErrorPage("server_error", "Authorization request incomplete"), 500));
37092
37230
  return;
37093
37231
  }
37094
- const federatedSession = (0, import_auth22.createFederatedAuthSession)({
37232
+ const federatedSession = (0, import_auth23.createFederatedAuthSession)({
37095
37233
  pendingAuthId: this.state.required.pendingAuthId || (0, import_utils67.randomUUID)(),
37096
37234
  clientId,
37097
37235
  redirectUri,
@@ -37120,7 +37258,7 @@ var init_oauth_callback_flow = __esm({
37120
37258
  method: "S256"
37121
37259
  };
37122
37260
  const providerState = `federated:${federatedSession.id}:${(0, import_utils67.randomUUID)()}`;
37123
- (0, import_auth22.startNextProvider)(federatedSession, pkce, providerState);
37261
+ (0, import_auth23.startNextProvider)(federatedSession, pkce, providerState);
37124
37262
  await sessionStore.update(federatedSession);
37125
37263
  const providerConfig = localAuth.getProviderConfig(firstProviderId);
37126
37264
  if (!providerConfig) {
@@ -37424,7 +37562,7 @@ var init_oauth_callback_flow = __esm({
37424
37562
  fields: login?.fields ?? {},
37425
37563
  error: failure.message
37426
37564
  };
37427
- return (0, import_auth22.renderLocalLoginPage)(login, ctx, fields);
37565
+ return (0, import_auth23.renderLocalLoginPage)(login, ctx, fields);
37428
37566
  }
37429
37567
  /**
37430
37568
  * Render a custom `@AuthUi({ slot: 'consent' })` page when one is registered.
@@ -37520,7 +37658,7 @@ var init_oauth_callback_flow = __esm({
37520
37658
  if (pendingAuth.authUiCsrf) {
37521
37659
  hiddenFields.push({ name: "csrf", value: pendingAuth.authUiCsrf });
37522
37660
  }
37523
- return (0, import_auth22.buildToolConsentPage)({
37661
+ return (0, import_auth23.buildToolConsentPage)({
37524
37662
  tools: toolCards,
37525
37663
  clientName: pendingAuth.clientId,
37526
37664
  pendingAuthId: pendingAuth.id,
@@ -37553,8 +37691,8 @@ var init_oauth_callback_flow = __esm({
37553
37691
  * Render an error page
37554
37692
  */
37555
37693
  renderErrorPage(error, description) {
37556
- const safeError = (0, import_auth22.escapeHtml)(error);
37557
- const safeDescription = (0, import_auth22.escapeHtml)(description);
37694
+ const safeError = (0, import_auth23.escapeHtml)(error);
37695
+ const safeDescription = (0, import_auth23.escapeHtml)(description);
37558
37696
  return `<!DOCTYPE html>
37559
37697
  <html lang="en">
37560
37698
  <head>
@@ -37612,6 +37750,7 @@ var init_oauth_callback_flow = __esm({
37612
37750
  "pending_auth_id",
37613
37751
  "incremental",
37614
37752
  "app_id",
37753
+ "ticket",
37615
37754
  "federated",
37616
37755
  "providers",
37617
37756
  "tools",
@@ -37664,11 +37803,11 @@ var init_oauth_callback_flow = __esm({
37664
37803
  });
37665
37804
 
37666
37805
  // libs/sdk/src/auth/flows/oauth.connect.flow.ts
37667
- var import_auth23, import_lazy_zod66, inputSchema18, stateSchema17, outputSchema17, plan17, name17, Stage17, RESERVED_CONNECT_PARAMS, OauthConnectFlow;
37806
+ var import_auth24, import_lazy_zod66, inputSchema18, stateSchema17, outputSchema17, plan17, name17, Stage17, RESERVED_CONNECT_PARAMS, OauthConnectFlow;
37668
37807
  var init_oauth_connect_flow = __esm({
37669
37808
  "libs/sdk/src/auth/flows/oauth.connect.flow.ts"() {
37670
37809
  "use strict";
37671
- import_auth23 = require("@frontmcp/auth");
37810
+ import_auth24 = require("@frontmcp/auth");
37672
37811
  import_lazy_zod66 = require("@frontmcp/lazy-zod");
37673
37812
  init_common();
37674
37813
  inputSchema18 = httpInputSchema;
@@ -37778,7 +37917,7 @@ var init_oauth_connect_flow = __esm({
37778
37917
  }
37779
37918
  this.logger.info(`Connected ${stored} credential(s) mid-session for key "${payload.key}"`);
37780
37919
  this.notifyResourcesUpdated(payload.key);
37781
- this.respond(httpRespond.html((0, import_auth23.buildConnectSuccessPage)({ key: payload.key })));
37920
+ this.respond(httpRespond.html((0, import_auth24.buildConnectSuccessPage)({ key: payload.key })));
37782
37921
  }
37783
37922
  // ============================================
37784
37923
  // Helpers
@@ -37786,7 +37925,7 @@ var init_oauth_connect_flow = __esm({
37786
37925
  /** Verify the signed resume token against the server secret (constant-time + expiry). */
37787
37926
  verifyToken(token, localAuth) {
37788
37927
  const secret = new TextDecoder().decode(localAuth.secret);
37789
- return (0, import_auth23.verifyCredentialResumeToken)(token, secret);
37928
+ return (0, import_auth24.verifyCredentialResumeToken)(token, secret);
37790
37929
  }
37791
37930
  /** Resolve the single credential field to render from `login.fields` (first field), or a default. */
37792
37931
  resolveConnectField(login, key) {
@@ -37802,8 +37941,8 @@ var init_oauth_connect_flow = __esm({
37802
37941
  renderConnectPage(payload, login, token, error) {
37803
37942
  const { name: name42, def } = this.resolveConnectField(login, payload.key);
37804
37943
  const action = `${this.scope.fullPath}/oauth/connect`;
37805
- const [field] = (0, import_auth23.toLoginExtraFields)({ [name42]: def });
37806
- return (0, import_auth23.buildConnectPage)({
37944
+ const [field] = (0, import_auth24.toLoginExtraFields)({ [name42]: def });
37945
+ return (0, import_auth24.buildConnectPage)({
37807
37946
  token,
37808
37947
  field,
37809
37948
  action,
@@ -37902,8 +38041,8 @@ var init_oauth_connect_flow = __esm({
37902
38041
  return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Connect Error</title></head>
37903
38042
  <body style="font-family:-apple-system,sans-serif;padding:40px;text-align:center">
37904
38043
  <h1 style="color:#e53e3e">Connect Error</h1>
37905
- <p><code>${(0, import_auth23.escapeHtml)(error)}</code></p>
37906
- <p>${(0, import_auth23.escapeHtml)(description)}</p>
38044
+ <p><code>${(0, import_auth24.escapeHtml)(error)}</code></p>
38045
+ <p>${(0, import_auth24.escapeHtml)(description)}</p>
37907
38046
  </body></html>`;
37908
38047
  }
37909
38048
  };
@@ -37930,11 +38069,11 @@ var init_oauth_connect_flow = __esm({
37930
38069
  });
37931
38070
 
37932
38071
  // libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts
37933
- var import_auth24, import_lazy_zod67, import_utils68, inputSchema19, stateSchema18, outputSchema18, plan18, name18, Stage18, OauthProviderCallbackFlow;
38072
+ var import_auth25, import_lazy_zod67, import_utils68, inputSchema19, stateSchema18, outputSchema18, plan18, name18, Stage18, OauthProviderCallbackFlow;
37934
38073
  var init_oauth_provider_callback_flow = __esm({
37935
38074
  "libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts"() {
37936
38075
  "use strict";
37937
- import_auth24 = require("@frontmcp/auth");
38076
+ import_auth25 = require("@frontmcp/auth");
37938
38077
  import_lazy_zod67 = require("@frontmcp/lazy-zod");
37939
38078
  import_utils68 = require("@frontmcp/utils");
37940
38079
  init_common();
@@ -38037,7 +38176,7 @@ var init_oauth_provider_callback_flow = __esm({
38037
38176
  );
38038
38177
  return;
38039
38178
  }
38040
- if (!(0, import_auth24.isSessionComplete)(session)) {
38179
+ if (!(0, import_auth25.isSessionComplete)(session)) {
38041
38180
  this.respond(
38042
38181
  httpRespond.html(this.renderErrorPage("invalid_request", "Authentication is still in progress."), 400)
38043
38182
  );
@@ -38236,7 +38375,7 @@ var init_oauth_provider_callback_flow = __esm({
38236
38375
  this.logger.info(`Stored tokens for provider ${providerId} (temp auth: ${tempAuthId})`);
38237
38376
  }
38238
38377
  if (tokens) {
38239
- (0, import_auth24.completeCurrentProvider)(session, tokens, userInfo);
38378
+ (0, import_auth25.completeCurrentProvider)(session, tokens, userInfo);
38240
38379
  if (userInfo) {
38241
38380
  if (!session.userInfo.sub && userInfo.sub) {
38242
38381
  session.userInfo.sub = userInfo.sub;
@@ -38269,11 +38408,11 @@ var init_oauth_provider_callback_flow = __esm({
38269
38408
  return;
38270
38409
  }
38271
38410
  const localAuth = this.getLocalAuth();
38272
- if ((0, import_auth24.isSessionComplete)(session)) {
38411
+ if ((0, import_auth25.isSessionComplete)(session)) {
38273
38412
  this.logger.info("All providers authenticated, completing federated auth flow");
38274
38413
  return this.completeFederatedAuth(session);
38275
38414
  }
38276
- const nextProviderId = (0, import_auth24.getNextProvider)(session);
38415
+ const nextProviderId = (0, import_auth25.getNextProvider)(session);
38277
38416
  if (!nextProviderId) {
38278
38417
  return this.completeFederatedAuth(session);
38279
38418
  }
@@ -38285,7 +38424,7 @@ var init_oauth_provider_callback_flow = __esm({
38285
38424
  method: "S256"
38286
38425
  };
38287
38426
  const providerState = `federated:${session.id}:${(0, import_utils68.randomUUID)()}`;
38288
- (0, import_auth24.startNextProvider)(session, pkce, providerState);
38427
+ (0, import_auth25.startNextProvider)(session, pkce, providerState);
38289
38428
  const sessionStore = localAuth.federatedSessionStore;
38290
38429
  if (sessionStore) {
38291
38430
  await sessionStore.update(session);
@@ -38448,7 +38587,7 @@ var init_oauth_provider_callback_flow = __esm({
38448
38587
  renderConsentScreen(session, consentConfig, error, preSelectedTools) {
38449
38588
  const callbackPath = `${this.scope.fullPath}/oauth/provider/_consent/callback`;
38450
38589
  const { toolCards } = projectConsentTools(this.scope, consentConfig.excludedTools);
38451
- return (0, import_auth24.buildToolConsentPage)({
38590
+ return (0, import_auth25.buildToolConsentPage)({
38452
38591
  tools: toolCards,
38453
38592
  clientName: session.clientId,
38454
38593
  // The pending_auth_id input is unused on the federated round-trip (we key
@@ -38485,8 +38624,8 @@ var init_oauth_provider_callback_flow = __esm({
38485
38624
  * Render an error page
38486
38625
  */
38487
38626
  renderErrorPage(error, description) {
38488
- const safeError = (0, import_auth24.escapeHtml)(error);
38489
- const safeDescription = (0, import_auth24.escapeHtml)(description);
38627
+ const safeError = (0, import_auth25.escapeHtml)(error);
38628
+ const safeDescription = (0, import_auth25.escapeHtml)(description);
38490
38629
  return `<!DOCTYPE html>
38491
38630
  <html lang="en">
38492
38631
  <head>
@@ -39229,12 +39368,12 @@ var init_oauth_token_flow = __esm({
39229
39368
  });
39230
39369
 
39231
39370
  // libs/sdk/src/auth/flows/oauth.userinfo.flow.ts
39232
- var import_reflect_metadata16, import_auth25, import_lazy_zod70, inputSchema22, stateSchema21, outputSchema21, plan21, name21, Stage21, OauthUserInfoFlow;
39371
+ var import_reflect_metadata16, import_auth26, import_lazy_zod70, inputSchema22, stateSchema21, outputSchema21, plan21, name21, Stage21, OauthUserInfoFlow;
39233
39372
  var init_oauth_userinfo_flow = __esm({
39234
39373
  "libs/sdk/src/auth/flows/oauth.userinfo.flow.ts"() {
39235
39374
  "use strict";
39236
39375
  import_reflect_metadata16 = require("reflect-metadata");
39237
- import_auth25 = require("@frontmcp/auth");
39376
+ import_auth26 = require("@frontmcp/auth");
39238
39377
  import_lazy_zod70 = require("@frontmcp/lazy-zod");
39239
39378
  init_common();
39240
39379
  inputSchema22 = httpInputSchema;
@@ -39254,7 +39393,7 @@ var init_oauth_userinfo_flow = __esm({
39254
39393
  async parseInput() {
39255
39394
  const { request } = this.rawInput;
39256
39395
  const authorizationHeader = request.headers?.["authorization"] ?? void 0;
39257
- const token = (0, import_auth25.extractBearerToken)(authorizationHeader);
39396
+ const token = (0, import_auth26.extractBearerToken)(authorizationHeader);
39258
39397
  const baseUrl = getRequestBaseUrl(request, this.scope.entryPath);
39259
39398
  this.state.set({ token, baseUrl });
39260
39399
  }
@@ -39325,13 +39464,13 @@ var init_oauth_userinfo_flow = __esm({
39325
39464
  });
39326
39465
 
39327
39466
  // libs/sdk/src/auth/flows/session.verify.flow.ts
39328
- var import_reflect_metadata17, import_auth26, import_lazy_zod71, import_utils71, inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
39467
+ var import_reflect_metadata17, import_auth27, import_lazy_zod71, import_utils71, inputSchema23, stateSchema22, UnauthorizedSchema, AuthorizedSchema, ForbiddenSchema, sessionVerifyOutputSchema, plan22, name22, Stage22, warnedUnpinnedAudience, SessionVerifyFlow;
39329
39468
  var init_session_verify_flow = __esm({
39330
39469
  "libs/sdk/src/auth/flows/session.verify.flow.ts"() {
39331
39470
  "use strict";
39332
39471
  init_common();
39333
39472
  import_reflect_metadata17 = require("reflect-metadata");
39334
- import_auth26 = require("@frontmcp/auth");
39473
+ import_auth27 = require("@frontmcp/auth");
39335
39474
  import_lazy_zod71 = require("@frontmcp/lazy-zod");
39336
39475
  import_utils71 = require("@frontmcp/utils");
39337
39476
  init_notification_service();
@@ -39426,7 +39565,7 @@ var init_session_verify_flow = __esm({
39426
39565
  authMode,
39427
39566
  ...platformType !== "unknown" && { platformType }
39428
39567
  };
39429
- const sessionId = (0, import_auth26.encryptJson)(payload);
39568
+ const sessionId = (0, import_auth27.encryptJson)(payload);
39430
39569
  this.respond({
39431
39570
  kind: "authorized",
39432
39571
  authorization: {
@@ -39447,11 +39586,11 @@ var init_session_verify_flow = __esm({
39447
39586
  const sessionIdQuery = request.query["sessionId"];
39448
39587
  const sessionIdHeader = sessionIdRawHeader ?? sessionIdQuery ?? void 0;
39449
39588
  const sessionProtocol = httpTransportHeader ? "http" : sessionIdRawHeader ? "streamable-http" : sessionIdQuery ? "sse" : void 0;
39450
- const token = (0, import_auth26.extractBearerToken)(authorizationHeader);
39589
+ const token = (0, import_auth27.extractBearerToken)(authorizationHeader);
39451
39590
  const userAgent = request.headers?.["user-agent"] ?? void 0;
39452
39591
  const prmMetadataPath = `/.well-known/oauth-protected-resource${entryPath}${routeBase}`;
39453
39592
  const prmUrl = `${getRequestBaseUrl(request)}${prmMetadataPath}`;
39454
- const prmMetadataHeader = (0, import_auth26.buildUnauthorizedHeader)(prmUrl);
39593
+ const prmMetadataHeader = (0, import_auth27.buildUnauthorizedHeader)(prmUrl);
39455
39594
  this.logger.verbose("parseInput", {
39456
39595
  hasAuthHeader: !!authorizationHeader,
39457
39596
  hasToken: !!token,
@@ -39505,7 +39644,7 @@ var init_session_verify_flow = __esm({
39505
39644
  });
39506
39645
  }
39507
39646
  async verifyIfJwt() {
39508
- const jwks = this.get(import_auth26.JwksService);
39647
+ const jwks = this.get(import_auth27.JwksService);
39509
39648
  const token = this.state.token;
39510
39649
  if (!token) {
39511
39650
  this.logger.warn("verifyIfJwt: missing or empty bearer token, returning 401");
@@ -39515,11 +39654,11 @@ var init_session_verify_flow = __esm({
39515
39654
  });
39516
39655
  return;
39517
39656
  }
39518
- if (!(0, import_auth26.isJwt)(token)) {
39657
+ if (!(0, import_auth27.isJwt)(token)) {
39519
39658
  this.logger.warn("verifyIfJwt: token is not a JWT, returning 401");
39520
39659
  this.respond({
39521
39660
  kind: "unauthorized",
39522
- prmMetadataHeader: (0, import_auth26.buildInvalidTokenHeader)(this.state.required.prmUrl, "Token is not a valid JWT")
39661
+ prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(this.state.required.prmUrl, "Token is not a valid JWT")
39523
39662
  });
39524
39663
  return;
39525
39664
  }
@@ -39564,8 +39703,8 @@ var init_session_verify_flow = __esm({
39564
39703
  "transparent audience validation is deriving the expected audience from the request Host: set auth.expectedAudience or FRONTMCP_PUBLIC_URL to bind tokens to a fixed resource. X-Forwarded-Host is ignored unless FRONTMCP_TRUST_PROXY is enabled."
39565
39704
  );
39566
39705
  }
39567
- const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : (0, import_auth26.deriveExpectedAudience)(this.state.required.baseUrl);
39568
- const audResult = (0, import_auth26.validateAudience)(result.payload?.["aud"], {
39706
+ const expectedAudiences = configuredAudience ? Array.isArray(configuredAudience) ? configuredAudience : [configuredAudience] : (0, import_auth27.deriveExpectedAudience)(this.state.required.baseUrl);
39707
+ const audResult = (0, import_auth27.validateAudience)(result.payload?.["aud"], {
39569
39708
  expectedAudiences,
39570
39709
  // By default accept tokens with no `aud` claim: many IdPs omit it, and
39571
39710
  // rejecting them here would break existing transparent deployments.
@@ -39579,7 +39718,7 @@ var init_session_verify_flow = __esm({
39579
39718
  this.logger.warn("verifyIfJwt: audience validation failed", { error: audResult.error });
39580
39719
  this.respond({
39581
39720
  kind: "unauthorized",
39582
- prmMetadataHeader: (0, import_auth26.buildInvalidTokenHeader)(
39721
+ prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(
39583
39722
  this.state.required.prmUrl,
39584
39723
  audResult.error ?? "Token audience is not valid for this resource"
39585
39724
  )
@@ -39599,7 +39738,7 @@ var init_session_verify_flow = __esm({
39599
39738
  });
39600
39739
  this.respond({
39601
39740
  kind: "forbidden",
39602
- prmMetadataHeader: (0, import_auth26.buildInsufficientScopeHeader)(this.state.required.prmUrl, requiredScopes)
39741
+ prmMetadataHeader: (0, import_auth27.buildInsufficientScopeHeader)(this.state.required.prmUrl, requiredScopes)
39603
39742
  });
39604
39743
  return;
39605
39744
  }
@@ -39609,14 +39748,14 @@ var init_session_verify_flow = __esm({
39609
39748
  this.logger.warn("verifyIfJwt: JWT verification failed", { error: result.error });
39610
39749
  this.respond({
39611
39750
  kind: "unauthorized",
39612
- prmMetadataHeader: (0, import_auth26.buildInvalidTokenHeader)(
39751
+ prmMetadataHeader: (0, import_auth27.buildInvalidTokenHeader)(
39613
39752
  this.state.required.prmUrl,
39614
39753
  result.error ?? "Token verification failed"
39615
39754
  )
39616
39755
  });
39617
39756
  }
39618
39757
  async deriveUser() {
39619
- this.state.set("user", (0, import_auth26.deriveTypedUser)(this.state.required.jwtPayload ?? {}));
39758
+ this.state.set("user", (0, import_auth27.deriveTypedUser)(this.state.required.jwtPayload ?? {}));
39620
39759
  }
39621
39760
  async parseSessionHeader() {
39622
39761
  const {
@@ -39702,13 +39841,13 @@ var init_session_verify_flow = __esm({
39702
39841
  });
39703
39842
 
39704
39843
  // libs/sdk/src/auth/flows/well-known.jwks.flow.ts
39705
- var import_reflect_metadata18, import_auth27, import_lazy_zod72, inputSchema24, stateSchema23, outputSchema22, plan23, name23, Stage23, WellKnownJwksFlow;
39844
+ var import_reflect_metadata18, import_auth28, import_lazy_zod72, inputSchema24, stateSchema23, outputSchema22, plan23, name23, Stage23, WellKnownJwksFlow;
39706
39845
  var init_well_known_jwks_flow = __esm({
39707
39846
  "libs/sdk/src/auth/flows/well-known.jwks.flow.ts"() {
39708
39847
  "use strict";
39709
39848
  init_common();
39710
39849
  import_reflect_metadata18 = require("reflect-metadata");
39711
- import_auth27 = require("@frontmcp/auth");
39850
+ import_auth28 = require("@frontmcp/auth");
39712
39851
  import_lazy_zod72 = require("@frontmcp/lazy-zod");
39713
39852
  init_auth_internal_errors();
39714
39853
  inputSchema24 = httpInputSchema;
@@ -39734,7 +39873,7 @@ var init_well_known_jwks_flow = __esm({
39734
39873
  }
39735
39874
  async collectData() {
39736
39875
  const { isOrchestrated } = this.state.required;
39737
- const jwksSvc = this.get(import_auth27.JwksService);
39876
+ const jwksSvc = this.get(import_auth28.JwksService);
39738
39877
  if (isOrchestrated) {
39739
39878
  const keysDoc = await jwksSvc.getPublicJwks();
39740
39879
  if (!keysDoc?.keys || !Array.isArray(keysDoc.keys)) {
@@ -40064,9 +40203,9 @@ function createSecureStoreProviders(opts) {
40064
40203
  name: "SecureStoreBackend"
40065
40204
  };
40066
40205
  const accessorProvider = {
40067
- provide: import_auth28.SECURE_STORE_ACCESSOR,
40206
+ provide: import_auth29.SECURE_STORE_ACCESSOR,
40068
40207
  inject: () => [FRONTMCP_CONTEXT, FrontMcpLogger],
40069
- useFactory: (ctx, logger) => new import_auth28.SecureStoreAccessorImpl({
40208
+ useFactory: (ctx, logger) => new import_auth29.SecureStoreAccessorImpl({
40070
40209
  backend,
40071
40210
  scope,
40072
40211
  resolveSub: () => resolveRequestSub(ctx),
@@ -40079,11 +40218,11 @@ function createSecureStoreProviders(opts) {
40079
40218
  };
40080
40219
  return [backendProvider, accessorProvider];
40081
40220
  }
40082
- var import_auth28, import_di32, SECURE_STORE_BACKEND;
40221
+ var import_auth29, import_di32, SECURE_STORE_BACKEND;
40083
40222
  var init_secure_store_providers = __esm({
40084
40223
  "libs/sdk/src/auth/secure-store/secure-store.providers.ts"() {
40085
40224
  "use strict";
40086
- import_auth28 = require("@frontmcp/auth");
40225
+ import_auth29 = require("@frontmcp/auth");
40087
40226
  import_di32 = require("@frontmcp/di");
40088
40227
  init_common();
40089
40228
  init_frontmcp_context_provider();
@@ -40093,14 +40232,14 @@ var init_secure_store_providers = __esm({
40093
40232
  });
40094
40233
 
40095
40234
  // libs/sdk/src/auth/secure-store/secure-store.context-extension.ts
40096
- var import_auth29, secureStoreContextExtension;
40235
+ var import_auth30, secureStoreContextExtension;
40097
40236
  var init_secure_store_context_extension = __esm({
40098
40237
  "libs/sdk/src/auth/secure-store/secure-store.context-extension.ts"() {
40099
40238
  "use strict";
40100
- import_auth29 = require("@frontmcp/auth");
40239
+ import_auth30 = require("@frontmcp/auth");
40101
40240
  secureStoreContextExtension = {
40102
40241
  property: "secureStore",
40103
- token: import_auth29.SECURE_STORE_ACCESSOR,
40242
+ token: import_auth30.SECURE_STORE_ACCESSOR,
40104
40243
  errorMessage: "The secure store is not available. Ensure the server uses auth mode 'local' (or 'remote'); the secure store is enabled automatically there."
40105
40244
  };
40106
40245
  }
@@ -40135,12 +40274,15 @@ function validateAuthorizationIssuer(received, expected) {
40135
40274
  }
40136
40275
  return { ok: true };
40137
40276
  }
40138
- var import_jose, import_auth30, import_utils73, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
40277
+ function isUnsupportedOperation(error) {
40278
+ return error instanceof import_utils73.StorageNotSupportedError;
40279
+ }
40280
+ var import_jose, import_auth31, import_utils73, defaultNoAuthSecret, RESERVED_JWT_CLAIMS, LocalPrimaryAuth;
40139
40281
  var init_instance_local_primary_auth = __esm({
40140
40282
  "libs/sdk/src/auth/instances/instance.local-primary-auth.ts"() {
40141
40283
  "use strict";
40142
40284
  import_jose = require("jose");
40143
- import_auth30 = require("@frontmcp/auth");
40285
+ import_auth31 = require("@frontmcp/auth");
40144
40286
  import_utils73 = require("@frontmcp/utils");
40145
40287
  init_common();
40146
40288
  init_auth2();
@@ -40196,17 +40338,17 @@ var init_instance_local_primary_auth = __esm({
40196
40338
  this.secret = getDefaultNoAuthSecret();
40197
40339
  }
40198
40340
  this.tokenStorage = this.readTokenStorage(options);
40199
- this.authorizationStoreImpl = new import_auth30.InMemoryAuthorizationStore();
40200
- this.federatedSessionStoreImpl = new import_auth30.InMemoryFederatedAuthSessionStore();
40201
- this.orchestratedTokenStoreImpl = new import_auth30.InMemoryOrchestratedTokenStore({
40341
+ this.authorizationStoreImpl = new import_auth31.InMemoryAuthorizationStore();
40342
+ this.federatedSessionStoreImpl = new import_auth31.InMemoryFederatedAuthSessionStore();
40343
+ this.orchestratedTokenStoreImpl = new import_auth31.InMemoryOrchestratedTokenStore({
40202
40344
  encryptionKey: this.secret
40203
40345
  // Reuse JWT secret for token encryption
40204
40346
  });
40205
- this.consentStoreImpl = new import_auth30.InMemoryConsentStore();
40206
- this.dcrClientRegistryImpl = new import_auth30.DcrClientRegistry(this.getDcrConfig() ?? {});
40347
+ this.consentStoreImpl = new import_auth31.InMemoryConsentStore();
40348
+ this.dcrClientRegistryImpl = new import_auth31.DcrClientRegistry(this.getDcrConfig() ?? {});
40207
40349
  if ((0, import_auth10.isOrchestratedMode)(options)) {
40208
40350
  const cimdConfig = options.cimd;
40209
- this.cimdService = new import_auth18.CimdService(this.logger, cimdConfig);
40351
+ this.cimdService = new import_auth19.CimdService(this.logger, cimdConfig);
40210
40352
  }
40211
40353
  this.ready = this.initialize();
40212
40354
  }
@@ -40218,7 +40360,7 @@ var init_instance_local_primary_auth = __esm({
40218
40360
  keys = [];
40219
40361
  secret;
40220
40362
  logger;
40221
- jwks = new import_auth30.JwksService();
40363
+ jwks = new import_auth31.JwksService();
40222
40364
  cimdService;
40223
40365
  /**
40224
40366
  * Token storage backend selected from `options.tokenStorage`.
@@ -40249,6 +40391,17 @@ var init_instance_local_primary_auth = __esm({
40249
40391
  dcrClientRegistryImpl;
40250
40392
  /** Storage adapter backing the persistent stores (kept for disposal). */
40251
40393
  storageAdapter;
40394
+ /**
40395
+ * Replay guard for incremental-authorization tickets (GHSA-2c4g-9c8x-6m8g).
40396
+ * Shares the persistent adapter when one is configured so the guard holds
40397
+ * across a distributed deployment; falls back to a dedicated in-memory
40398
+ * adapter, matching how the credential vault selects its backing store.
40399
+ *
40400
+ * Holds the in-flight PROMISE, not the resolved adapter: two concurrent
40401
+ * claims for the same ticket must not each build their own store, or each
40402
+ * would write and read back its own nonce and both would win.
40403
+ */
40404
+ ticketReplayStorage;
40252
40405
  /**
40253
40406
  * Per-session encrypted credential vault (Checkpoint 3b). Backs
40254
40407
  * `this.credentials` in tools and persists `authenticate()` credentials.
@@ -40386,19 +40539,19 @@ var init_instance_local_primary_auth = __esm({
40386
40539
  * key, so upstream provider tokens stay encrypted at rest in every backend.
40387
40540
  */
40388
40541
  async initializeStores() {
40389
- if (!(0, import_auth30.isPersistentTokenStorage)(this.tokenStorage)) {
40542
+ if (!(0, import_auth31.isPersistentTokenStorage)(this.tokenStorage)) {
40390
40543
  return;
40391
40544
  }
40392
40545
  try {
40393
- const adapter = await (0, import_auth30.createTokenStorageAdapter)(this.tokenStorage);
40546
+ const adapter = await (0, import_auth31.createTokenStorageAdapter)(this.tokenStorage);
40394
40547
  this.storageAdapter = adapter;
40395
- this.authorizationStoreImpl = new import_auth30.StorageAuthorizationStore(adapter);
40396
- this.federatedSessionStoreImpl = new import_auth30.StorageFederatedAuthSessionStore(adapter);
40397
- this.orchestratedTokenStoreImpl = new import_auth30.StorageOrchestratedTokenStore(adapter, {
40548
+ this.authorizationStoreImpl = new import_auth31.StorageAuthorizationStore(adapter);
40549
+ this.federatedSessionStoreImpl = new import_auth31.StorageFederatedAuthSessionStore(adapter);
40550
+ this.orchestratedTokenStoreImpl = new import_auth31.StorageOrchestratedTokenStore(adapter, {
40398
40551
  encryptionKey: this.secret
40399
40552
  });
40400
- this.consentStoreImpl = new import_auth30.StorageConsentStore(adapter);
40401
- const backend = (0, import_auth30.isSqliteTokenStorage)(this.tokenStorage) ? "sqlite" : (0, import_auth30.isRedisTokenStorage)(this.tokenStorage) ? "redis" : "unknown";
40553
+ this.consentStoreImpl = new import_auth31.StorageConsentStore(adapter);
40554
+ const backend = (0, import_auth31.isSqliteTokenStorage)(this.tokenStorage) ? "sqlite" : (0, import_auth31.isRedisTokenStorage)(this.tokenStorage) ? "redis" : "unknown";
40402
40555
  this.logger.info(`Token storage initialized with persistent backend: ${backend}`);
40403
40556
  } catch (err) {
40404
40557
  this.logger.error("Failed to initialize persistent token storage", err);
@@ -40416,6 +40569,81 @@ var init_instance_local_primary_auth = __esm({
40416
40569
  * (`this.secret`), so resume URLs are framework-signed with the same trust
40417
40570
  * root as the access tokens.
40418
40571
  */
40572
+ /**
40573
+ * The server HMAC signing key, as the string form the `signData`/`verifyData`
40574
+ * helpers take. These are the exact bytes the access tokens are signed with,
40575
+ * so framework-signed URLs share the access tokens' trust root.
40576
+ */
40577
+ get signingSecret() {
40578
+ return new TextDecoder().decode(this.secret);
40579
+ }
40580
+ /**
40581
+ * Claim an incremental-authorization ticket, exactly once.
40582
+ *
40583
+ * A signature check proves a ticket was minted by this server for a verified
40584
+ * subject; it cannot prove the ticket has not already been used. The `auth_url`
40585
+ * carrying the ticket travels through agent transcripts and server logs, so a
40586
+ * leaked ticket must not stay usable for the rest of its TTL.
40587
+ *
40588
+ * The claim is race-free without a transaction: each attempt writes its OWN
40589
+ * nonce under `ifNotExists` and reads the key back. Only the attempt whose
40590
+ * nonce survives won, so concurrent replays all lose.
40591
+ *
40592
+ * @returns true when this caller claimed the ticket, false when it was already used.
40593
+ */
40594
+ async claimIncrementalTicket(jti, ttlMs) {
40595
+ const key = `incremental-ticket:${jti}`;
40596
+ const nonce = (0, import_utils73.randomUUID)();
40597
+ const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
40598
+ let storage = await this.getTicketReplayStorage();
40599
+ try {
40600
+ await storage.set(key, nonce, { ifNotExists: true, ttlSeconds });
40601
+ } catch (error) {
40602
+ if (!isUnsupportedOperation(error)) {
40603
+ this.logger.warn(`Incremental ticket replay guard unavailable: ${String(error)}`);
40604
+ return false;
40605
+ }
40606
+ this.logger.warn(
40607
+ "Incremental ticket replay guard: the configured storage has no conditional write; falling back to an in-memory guard (single use holds within this instance only)."
40608
+ );
40609
+ storage = await this.useInMemoryTicketReplayStorage();
40610
+ try {
40611
+ await storage.set(key, nonce, { ifNotExists: true, ttlSeconds });
40612
+ } catch (fallbackError) {
40613
+ this.logger.warn(`Incremental ticket replay guard unavailable: ${String(fallbackError)}`);
40614
+ return false;
40615
+ }
40616
+ }
40617
+ try {
40618
+ return await storage.get(key) === nonce;
40619
+ } catch (error) {
40620
+ this.logger.warn(`Incremental ticket replay guard unavailable: ${String(error)}`);
40621
+ return false;
40622
+ }
40623
+ }
40624
+ getTicketReplayStorage() {
40625
+ if (!this.ticketReplayStorage) {
40626
+ if (this.storageAdapter) {
40627
+ this.ticketReplayStorage = Promise.resolve(this.storageAdapter);
40628
+ } else {
40629
+ this.logger.debug(
40630
+ "Incremental ticket replay guard is in-memory (no persistent tokenStorage configured); single use holds within this instance only."
40631
+ );
40632
+ this.ticketReplayStorage = this.useInMemoryTicketReplayStorage();
40633
+ }
40634
+ }
40635
+ return this.ticketReplayStorage;
40636
+ }
40637
+ /** Replace the replay guard with a fresh in-memory adapter, memoized. */
40638
+ useInMemoryTicketReplayStorage() {
40639
+ const pending = (async () => {
40640
+ const memory = new import_utils73.MemoryStorageAdapter();
40641
+ await memory.connect();
40642
+ return memory;
40643
+ })();
40644
+ this.ticketReplayStorage = pending;
40645
+ return pending;
40646
+ }
40419
40647
  async initializeCredentialVault() {
40420
40648
  if ((0, import_auth10.isPublicMode)(this.options)) {
40421
40649
  return;
@@ -40429,12 +40657,12 @@ var init_instance_local_primary_auth = __esm({
40429
40657
  storage = memory;
40430
40658
  }
40431
40659
  const pepper = (0, import_utils73.getEnv)("VAULT_SECRET") ?? (0, import_utils73.getEnv)("JWT_SECRET") ?? void 0;
40432
- this.credentialVaultImpl = new import_auth30.SessionCredentialVault({
40660
+ this.credentialVaultImpl = new import_auth31.SessionCredentialVault({
40433
40661
  storage,
40434
40662
  pepper,
40435
40663
  logger: this.logger.child("SessionCredentialVault")
40436
40664
  });
40437
- const signingSecret = new TextDecoder().decode(this.secret);
40665
+ const signingSecret = this.signingSecret;
40438
40666
  const basePath = this.issuer;
40439
40667
  await this.providers.addDynamicProviders(
40440
40668
  createCredentialsProviders({
@@ -40480,7 +40708,7 @@ var init_instance_local_primary_auth = __esm({
40480
40708
  const config = this.readSecureStoreConfig();
40481
40709
  const reuseAdapter = this.storageAdapter && this.secureStoreSharesTokenStorage(config) ? this.storageAdapter : void 0;
40482
40710
  const pepper = (0, import_utils73.getEnv)("VAULT_SECRET") ?? (0, import_utils73.getEnv)("JWT_SECRET") ?? new TextDecoder().decode(this.secret);
40483
- const resolved = await (0, import_auth30.createSecureStore)({
40711
+ const resolved = await (0, import_auth31.createSecureStore)({
40484
40712
  config,
40485
40713
  pepper,
40486
40714
  storage: reuseAdapter,
@@ -40507,8 +40735,8 @@ var init_instance_local_primary_auth = __esm({
40507
40735
  */
40508
40736
  secureStoreSharesTokenStorage(config) {
40509
40737
  if (typeof config !== "object" || config === null) return false;
40510
- if ("sqlite" in config && config.sqlite) return (0, import_auth30.isSqliteTokenStorage)(this.tokenStorage);
40511
- if ("redis" in config && config.redis) return (0, import_auth30.isRedisTokenStorage)(this.tokenStorage);
40738
+ if ("sqlite" in config && config.sqlite) return (0, import_auth31.isSqliteTokenStorage)(this.tokenStorage);
40739
+ if ("redis" in config && config.redis) return (0, import_auth31.isRedisTokenStorage)(this.tokenStorage);
40512
40740
  return false;
40513
40741
  }
40514
40742
  async signAnonymousJwt() {
@@ -40636,7 +40864,7 @@ var init_instance_local_primary_auth = __esm({
40636
40864
  error_description: "Redirect URI does not match"
40637
40865
  };
40638
40866
  }
40639
- if (!(0, import_auth30.verifyPkce)(codeVerifier, codeRecord.pkce)) {
40867
+ if (!(0, import_auth31.verifyPkce)(codeVerifier, codeRecord.pkce)) {
40640
40868
  this.logger.warn(`PKCE verification failed`);
40641
40869
  return {
40642
40870
  error: "invalid_grant",
@@ -40807,7 +41035,7 @@ var init_instance_local_primary_auth = __esm({
40807
41035
  scope: import_di4.ProviderScope.GLOBAL,
40808
41036
  name: "auth:jwk-service"
40809
41037
  },
40810
- provide: import_auth30.JwksService
41038
+ provide: import_auth31.JwksService
40811
41039
  });
40812
41040
  if (this.cimdService) {
40813
41041
  this.providers.injectProvider({
@@ -40816,7 +41044,7 @@ var init_instance_local_primary_auth = __esm({
40816
41044
  scope: import_di4.ProviderScope.GLOBAL,
40817
41045
  name: "auth:cimd-service"
40818
41046
  },
40819
- provide: import_auth18.CimdService
41047
+ provide: import_auth19.CimdService
40820
41048
  });
40821
41049
  this.logger.debug("CIMD service registered");
40822
41050
  }
@@ -41182,11 +41410,11 @@ var init_instance_local_primary_auth = __esm({
41182
41410
  });
41183
41411
 
41184
41412
  // libs/sdk/src/auth/instances/instance.remote-primary-auth.ts
41185
- var import_auth32, RemotePrimaryAuth;
41413
+ var import_auth33, RemotePrimaryAuth;
41186
41414
  var init_instance_remote_primary_auth = __esm({
41187
41415
  "libs/sdk/src/auth/instances/instance.remote-primary-auth.ts"() {
41188
41416
  "use strict";
41189
- import_auth32 = require("@frontmcp/auth");
41417
+ import_auth33 = require("@frontmcp/auth");
41190
41418
  init_common();
41191
41419
  init_session_verify_flow();
41192
41420
  init_well_known_jwks_flow();
@@ -41202,7 +41430,7 @@ var init_instance_remote_primary_auth = __esm({
41202
41430
  scope;
41203
41431
  providers;
41204
41432
  ready;
41205
- jwks = new import_auth32.JwksService();
41433
+ jwks = new import_auth33.JwksService();
41206
41434
  fetch(input, init) {
41207
41435
  return fetch(input, init);
41208
41436
  }
@@ -41220,7 +41448,7 @@ var init_instance_remote_primary_auth = __esm({
41220
41448
  scope: import_di4.ProviderScope.GLOBAL,
41221
41449
  name: "auth:jwk-service"
41222
41450
  },
41223
- provide: import_auth32.JwksService
41451
+ provide: import_auth33.JwksService
41224
41452
  });
41225
41453
  await this.registerAuthFlows(scope);
41226
41454
  return Promise.resolve();
@@ -41238,12 +41466,12 @@ var init_instance_remote_primary_auth = __esm({
41238
41466
  });
41239
41467
 
41240
41468
  // libs/sdk/src/auth/auth.registry.ts
41241
- var import_reflect_metadata21, import_auth33, import_di33, DEFAULT_AUTH_OPTIONS, AuthRegistry;
41469
+ var import_reflect_metadata21, import_auth34, import_di33, DEFAULT_AUTH_OPTIONS, AuthRegistry;
41242
41470
  var init_auth_registry = __esm({
41243
41471
  "libs/sdk/src/auth/auth.registry.ts"() {
41244
41472
  "use strict";
41245
41473
  import_reflect_metadata21 = require("reflect-metadata");
41246
- import_auth33 = require("@frontmcp/auth");
41474
+ import_auth34 = require("@frontmcp/auth");
41247
41475
  import_di33 = require("@frontmcp/di");
41248
41476
  init_common();
41249
41477
  init_context_extension();
@@ -41275,7 +41503,7 @@ var init_auth_registry = __esm({
41275
41503
  this.logger = providers.get(FrontMcpLogger).child("AuthRegistry");
41276
41504
  this.parsedOptions = (0, import_auth10.parseAuthOptions)(primaryInput ?? DEFAULT_AUTH_OPTIONS);
41277
41505
  const appAuthInfos = this.extractAppAuthInfo(scope);
41278
- this.detection = (0, import_auth33.detectAuthProviders)(this.parsedOptions, appAuthInfos);
41506
+ this.detection = (0, import_auth34.detectAuthProviders)(this.parsedOptions, appAuthInfos);
41279
41507
  this.requiresOrchestration = this.detection.requiresOrchestration;
41280
41508
  this.logDetectionResults();
41281
41509
  this.validateConfiguration();
@@ -44733,6 +44961,7 @@ var init_execute_workflow_tool = __esm({
44733
44961
  "use strict";
44734
44962
  import_lazy_zod81 = require("@frontmcp/lazy-zod");
44735
44963
  init_common();
44964
+ init_errors();
44736
44965
  ExecuteWorkflowTool = class extends ToolContext {
44737
44966
  async execute(input) {
44738
44967
  const scope = this.scope;
@@ -44744,12 +44973,13 @@ var init_execute_workflow_tool = __esm({
44744
44973
  }
44745
44974
  const workflow = workflowRegistry.findByName(input.name);
44746
44975
  if (!workflow) {
44747
- return this.fail(new Error(`Workflow "${input.name}" not found`));
44976
+ return this.fail(new JobNotAuthorizedError(input.name));
44748
44977
  }
44749
44978
  const result = await executionManager.executeWorkflow(workflow, jobRegistry, {
44750
44979
  background: input.background,
44751
44980
  sessionId: this.authInfo.sessionId,
44752
44981
  authInfo: this.authInfo,
44982
+ authoritiesContextBuilder: this.scope.authoritiesContextBuilder,
44753
44983
  workflowInput: input.input
44754
44984
  });
44755
44985
  return result;
@@ -44774,6 +45004,55 @@ var init_execute_workflow_tool = __esm({
44774
45004
  }
44775
45005
  });
44776
45006
 
45007
+ // libs/sdk/src/common/utils/principal.utils.ts
45008
+ function toStringArray(value) {
45009
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
45010
+ if (typeof value === "string" && value.length > 0) return value.split(" ").filter(Boolean);
45011
+ return [];
45012
+ }
45013
+ function asRecord(value) {
45014
+ return value && typeof value === "object" ? value : void 0;
45015
+ }
45016
+ function firstPresent(sources, convert) {
45017
+ for (const source of sources) {
45018
+ if (source !== void 0 && source !== null) return convert(source);
45019
+ }
45020
+ return [];
45021
+ }
45022
+ function resolvePrincipal2(authInfo, contextBuilder) {
45023
+ const info = authInfo ?? {};
45024
+ const user = asRecord(info["user"]) ?? {};
45025
+ const extraAuthorization = asRecord(asRecord(info["extra"])?.["authorization"]);
45026
+ const authInfoClaims = asRecord(info["claims"]) ?? {};
45027
+ const authorizationClaims = asRecord(extraAuthorization?.["claims"]) ?? {};
45028
+ const claims = { ...authorizationClaims, ...authInfoClaims, ...user };
45029
+ const scopes = toStringArray(info["scopes"]).length ? toStringArray(info["scopes"]) : firstPresent([extraAuthorization?.["scopes"], claims["scope"], claims["scopes"]], toStringArray);
45030
+ if (contextBuilder) {
45031
+ const ctx = contextBuilder.build(info);
45032
+ return {
45033
+ sub: ctx.user.sub,
45034
+ roles: ctx.user.roles,
45035
+ permissions: ctx.user.permissions,
45036
+ scopes,
45037
+ claims: ctx.user.claims
45038
+ };
45039
+ }
45040
+ const roles = firstPresent([user["roles"], claims["roles"], extraAuthorization?.["scopes"]], toStringArray);
45041
+ const permissions = firstPresent([user["permissions"], claims["permissions"]], toStringArray);
45042
+ return {
45043
+ sub: typeof user["sub"] === "string" ? user["sub"] : "",
45044
+ roles,
45045
+ permissions,
45046
+ scopes,
45047
+ claims
45048
+ };
45049
+ }
45050
+ var init_principal_utils = __esm({
45051
+ "libs/sdk/src/common/utils/principal.utils.ts"() {
45052
+ "use strict";
45053
+ }
45054
+ });
45055
+
44777
45056
  // libs/sdk/src/workflow/tools/get-workflow-status.tool.ts
44778
45057
  var import_lazy_zod82, GetWorkflowStatusTool;
44779
45058
  var init_get_workflow_status_tool = __esm({
@@ -44781,6 +45060,7 @@ var init_get_workflow_status_tool = __esm({
44781
45060
  "use strict";
44782
45061
  import_lazy_zod82 = require("@frontmcp/lazy-zod");
44783
45062
  init_common();
45063
+ init_principal_utils();
44784
45064
  GetWorkflowStatusTool = class extends ToolContext {
44785
45065
  async execute(input) {
44786
45066
  const scope = this.scope;
@@ -44789,7 +45069,7 @@ var init_get_workflow_status_tool = __esm({
44789
45069
  return this.fail(new Error("Jobs system is not enabled"));
44790
45070
  }
44791
45071
  const record = await executionManager.getStatus(input.runId);
44792
- if (!record) {
45072
+ if (!record || !this.ownsRun(record)) {
44793
45073
  return this.fail(new Error(`Run "${input.runId}" not found`));
44794
45074
  }
44795
45075
  const workflowRecord = record;
@@ -44802,6 +45082,23 @@ var init_get_workflow_status_tool = __esm({
44802
45082
  completedAt: record.completedAt
44803
45083
  };
44804
45084
  }
45085
+ /**
45086
+ * Match on the owning subject, falling back to the session for runs started
45087
+ * before an owner was recorded (and for anonymous/public servers, where every
45088
+ * caller has an empty subject and the session is the only identity there is).
45089
+ *
45090
+ * A record carrying NEITHER identity is refused. Such a run cannot be
45091
+ * attributed to anyone, and its inputs and results are exactly what this check
45092
+ * exists to protect — anyone holding the id would otherwise be able to read
45093
+ * it. Internal callers that must read unattributed runs should go through the
45094
+ * execution manager directly rather than this tool.
45095
+ */
45096
+ ownsRun(record) {
45097
+ const callerSub = resolvePrincipal2(this.authInfo, this.scope.authoritiesContextBuilder).sub;
45098
+ if (record.ownerSub) return record.ownerSub === callerSub;
45099
+ if (record.sessionId) return record.sessionId === this.authInfo.sessionId;
45100
+ return false;
45101
+ }
44805
45102
  };
44806
45103
  GetWorkflowStatusTool = __decorateClass([
44807
45104
  FrontMcpTool({
@@ -44829,6 +45126,53 @@ var init_get_workflow_status_tool = __esm({
44829
45126
  }
44830
45127
  });
44831
45128
 
45129
+ // libs/sdk/src/job/job-permission.guard.ts
45130
+ var JobPermissionGuard;
45131
+ var init_job_permission_guard = __esm({
45132
+ "libs/sdk/src/job/job-permission.guard.ts"() {
45133
+ "use strict";
45134
+ init_principal_utils();
45135
+ JobPermissionGuard = class _JobPermissionGuard {
45136
+ /**
45137
+ * Check whether the caller may perform `action`.
45138
+ *
45139
+ * @param permissions - the entry's declared rules (undefined = unrestricted).
45140
+ * @param action - the action being attempted.
45141
+ * @param authInfo - the request's AuthInfo.
45142
+ * @param contextBuilder - the scope's authorities context builder, when configured.
45143
+ */
45144
+ static async check(permissions, action, authInfo, contextBuilder) {
45145
+ if (!permissions || permissions.length === 0) {
45146
+ return true;
45147
+ }
45148
+ const relevant = permissions.filter((p) => p.action === action);
45149
+ if (relevant.length === 0) {
45150
+ return true;
45151
+ }
45152
+ const principal = resolvePrincipal2(authInfo, contextBuilder);
45153
+ for (const perm of relevant) {
45154
+ const passed = await _JobPermissionGuard.checkSingle(perm, principal, authInfo);
45155
+ if (!passed) return false;
45156
+ }
45157
+ return true;
45158
+ }
45159
+ static async checkSingle(perm, principal, authInfo) {
45160
+ if (perm.roles && perm.roles.length > 0) {
45161
+ if (!perm.roles.some((role) => principal.roles.includes(role))) return false;
45162
+ }
45163
+ if (perm.scopes && perm.scopes.length > 0) {
45164
+ if (!perm.scopes.some((scope) => principal.scopes.includes(scope))) return false;
45165
+ }
45166
+ if (perm.custom) {
45167
+ const result = await perm.custom(authInfo ?? {});
45168
+ if (!result) return false;
45169
+ }
45170
+ return true;
45171
+ }
45172
+ };
45173
+ }
45174
+ });
45175
+
44832
45176
  // libs/sdk/src/workflow/tools/list-workflows.tool.ts
44833
45177
  var import_lazy_zod83, ListWorkflowsTool;
44834
45178
  var init_list_workflows_tool = __esm({
@@ -44836,6 +45180,7 @@ var init_list_workflows_tool = __esm({
44836
45180
  "use strict";
44837
45181
  import_lazy_zod83 = require("@frontmcp/lazy-zod");
44838
45182
  init_common();
45183
+ init_job_permission_guard();
44839
45184
  ListWorkflowsTool = class extends ToolContext {
44840
45185
  async execute(input) {
44841
45186
  const scope = this.scope;
@@ -44847,7 +45192,11 @@ var init_list_workflows_tool = __esm({
44847
45192
  tags: input.tags,
44848
45193
  labels: input.labels
44849
45194
  });
44850
- const mapped = workflows.map((w) => ({
45195
+ const visible = [];
45196
+ for (const workflow of workflows) {
45197
+ if (await this.maySee(workflow)) visible.push(workflow);
45198
+ }
45199
+ const mapped = visible.map((w) => ({
44851
45200
  name: w.name,
44852
45201
  description: w.metadata.description,
44853
45202
  trigger: w.getTrigger(),
@@ -44859,6 +45208,12 @@ var init_list_workflows_tool = __esm({
44859
45208
  count: mapped.length
44860
45209
  };
44861
45210
  }
45211
+ async maySee(workflow) {
45212
+ const permissions = workflow.metadata.permissions;
45213
+ const builder = this.scope.authoritiesContextBuilder;
45214
+ if (!await JobPermissionGuard.check(permissions, "list", this.authInfo, builder)) return false;
45215
+ return JobPermissionGuard.check(permissions, "execute", this.authInfo, builder);
45216
+ }
44862
45217
  };
44863
45218
  ListWorkflowsTool = __decorateClass([
44864
45219
  FrontMcpTool({
@@ -44894,6 +45249,7 @@ var init_register_workflow_tool = __esm({
44894
45249
  "use strict";
44895
45250
  import_lazy_zod84 = require("@frontmcp/lazy-zod");
44896
45251
  init_common();
45252
+ init_errors();
44897
45253
  RegisterWorkflowTool = class extends ToolContext {
44898
45254
  async execute(input) {
44899
45255
  const scope = this.scope;
@@ -44901,6 +45257,9 @@ var init_register_workflow_tool = __esm({
44901
45257
  if (!workflowRegistry) {
44902
45258
  return this.fail(new Error("Workflows system is not enabled"));
44903
45259
  }
45260
+ if (!this.dynamicRegistrationEnabled()) {
45261
+ return this.fail(new DynamicJobRegistrationDisabledError("workflow"));
45262
+ }
44904
45263
  const existing = workflowRegistry.findByName(input.name);
44905
45264
  if (existing) {
44906
45265
  return this.fail(new Error(`Workflow "${input.name}" already exists`));
@@ -44922,6 +45281,10 @@ var init_register_workflow_tool = __esm({
44922
45281
  });
44923
45282
  return { success: true, workflowId };
44924
45283
  }
45284
+ dynamicRegistrationEnabled() {
45285
+ const jobs = this.scope.metadata?.jobs;
45286
+ return jobs?.allowDynamicRegistration === true;
45287
+ }
44925
45288
  };
44926
45289
  RegisterWorkflowTool = __decorateClass([
44927
45290
  FrontMcpTool({
@@ -44960,6 +45323,8 @@ var init_remove_workflow_tool = __esm({
44960
45323
  "use strict";
44961
45324
  import_lazy_zod85 = require("@frontmcp/lazy-zod");
44962
45325
  init_common();
45326
+ init_errors();
45327
+ init_job_permission_guard();
44963
45328
  RemoveWorkflowTool = class extends ToolContext {
44964
45329
  async execute(input) {
44965
45330
  const scope = this.scope;
@@ -44969,7 +45334,16 @@ var init_remove_workflow_tool = __esm({
44969
45334
  }
44970
45335
  const workflow = workflowRegistry.findByName(input.name);
44971
45336
  if (!workflow) {
44972
- return this.fail(new Error(`Workflow "${input.name}" not found`));
45337
+ return this.fail(new JobNotAuthorizedError(input.name));
45338
+ }
45339
+ const allowed = await JobPermissionGuard.check(
45340
+ workflow.metadata.permissions,
45341
+ "delete",
45342
+ this.authInfo,
45343
+ this.scope.authoritiesContextBuilder
45344
+ );
45345
+ if (!allowed) {
45346
+ return this.fail(new JobNotAuthorizedError(input.name));
44973
45347
  }
44974
45348
  if (!workflow.isDynamic()) {
44975
45349
  return this.fail(new Error(`Workflow "${input.name}" is not dynamic and cannot be removed`));
@@ -45579,7 +45953,10 @@ var init_job_execution_manager = __esm({
45579
45953
  "libs/sdk/src/job/execution/job-execution.manager.ts"() {
45580
45954
  "use strict";
45581
45955
  import_utils79 = require("@frontmcp/utils");
45956
+ init_principal_utils();
45957
+ init_errors();
45582
45958
  init_workflow_engine();
45959
+ init_job_permission_guard();
45583
45960
  JobExecutionManager = class {
45584
45961
  stateStore;
45585
45962
  logger;
@@ -45593,6 +45970,7 @@ var init_job_execution_manager = __esm({
45593
45970
  * Execute a job inline (synchronous) or in background.
45594
45971
  */
45595
45972
  async executeJob(job, input, opts = {}) {
45973
+ await this.assertMayExecute(job.metadata.permissions, job.name, opts);
45596
45974
  const runId = (0, import_utils79.randomUUID)();
45597
45975
  const retryConfig = job.metadata.retry ?? {};
45598
45976
  const maxAttempts = retryConfig.maxAttempts ?? 1;
@@ -45601,6 +45979,7 @@ var init_job_execution_manager = __esm({
45601
45979
  jobId: job.metadata.id ?? job.name,
45602
45980
  jobName: job.name,
45603
45981
  sessionId: opts.sessionId,
45982
+ ownerSub: resolvePrincipal2(opts.authInfo, opts.authoritiesContextBuilder).sub || void 0,
45604
45983
  state: "pending",
45605
45984
  input,
45606
45985
  startedAt: Date.now(),
@@ -45631,6 +46010,7 @@ var init_job_execution_manager = __esm({
45631
46010
  * Execute a workflow inline or in background.
45632
46011
  */
45633
46012
  async executeWorkflow(workflow, jobRegistry, opts = {}) {
46013
+ await this.assertMayExecute(workflow.metadata.permissions, workflow.name, opts);
45634
46014
  const runId = (0, import_utils79.randomUUID)();
45635
46015
  const runRecord = {
45636
46016
  runId,
@@ -45638,6 +46018,7 @@ var init_job_execution_manager = __esm({
45638
46018
  jobName: workflow.name,
45639
46019
  workflowName: workflow.name,
45640
46020
  sessionId: opts.sessionId,
46021
+ ownerSub: resolvePrincipal2(opts.authInfo, opts.authoritiesContextBuilder).sub || void 0,
45641
46022
  state: "pending",
45642
46023
  input: opts.workflowInput,
45643
46024
  startedAt: Date.now(),
@@ -45665,6 +46046,25 @@ var init_job_execution_manager = __esm({
45665
46046
  }
45666
46047
  return this.executeWorkflowInline(workflow, jobRegistry, runId, opts);
45667
46048
  }
46049
+ /**
46050
+ * Throw unless the caller may execute the entry.
46051
+ *
46052
+ * Uses the same error for "not authorized" as the tools use for "not found",
46053
+ * so a caller cannot enumerate which restricted jobs exist by comparing
46054
+ * responses.
46055
+ */
46056
+ async assertMayExecute(permissions, name42, opts) {
46057
+ const allowed = await JobPermissionGuard.check(
46058
+ permissions,
46059
+ "execute",
46060
+ opts.authInfo,
46061
+ opts.authoritiesContextBuilder
46062
+ );
46063
+ if (!allowed) {
46064
+ this.logger.warn(`Execution of "${name42}" denied: caller does not satisfy its execute permissions`);
46065
+ throw new JobNotAuthorizedError(name42);
46066
+ }
46067
+ }
45668
46068
  /**
45669
46069
  * Get execution status.
45670
46070
  */
@@ -46690,6 +47090,7 @@ var init_execute_job_tool = __esm({
46690
47090
  "use strict";
46691
47091
  import_lazy_zod88 = require("@frontmcp/lazy-zod");
46692
47092
  init_common();
47093
+ init_errors();
46693
47094
  ExecuteJobTool = class extends ToolContext {
46694
47095
  async execute(input) {
46695
47096
  const scope = this.scope;
@@ -46700,12 +47101,13 @@ var init_execute_job_tool = __esm({
46700
47101
  }
46701
47102
  const job = jobRegistry.findByName(input.name);
46702
47103
  if (!job) {
46703
- return this.fail(new Error(`Job "${input.name}" not found`));
47104
+ return this.fail(new JobNotAuthorizedError(input.name));
46704
47105
  }
46705
47106
  const result = await executionManager.executeJob(job, input.input ?? {}, {
46706
47107
  background: input.background,
46707
47108
  sessionId: this.authInfo.sessionId,
46708
- authInfo: this.authInfo
47109
+ authInfo: this.authInfo,
47110
+ authoritiesContextBuilder: this.scope.authoritiesContextBuilder
46709
47111
  });
46710
47112
  return result;
46711
47113
  }
@@ -46737,6 +47139,7 @@ var init_get_job_status_tool = __esm({
46737
47139
  "use strict";
46738
47140
  import_lazy_zod89 = require("@frontmcp/lazy-zod");
46739
47141
  init_common();
47142
+ init_principal_utils();
46740
47143
  init_errors();
46741
47144
  GetJobStatusTool = class extends ToolContext {
46742
47145
  async execute(input) {
@@ -46746,7 +47149,7 @@ var init_get_job_status_tool = __esm({
46746
47149
  return this.fail(new GenericServerError("Jobs system is not enabled"));
46747
47150
  }
46748
47151
  const record = await executionManager.getStatus(input.runId);
46749
- if (!record) {
47152
+ if (!record || !this.ownsRun(record)) {
46750
47153
  return this.fail(new InvalidInputError(`Run "${input.runId}" not found`));
46751
47154
  }
46752
47155
  return {
@@ -46761,6 +47164,23 @@ var init_get_job_status_tool = __esm({
46761
47164
  logs: record.logs
46762
47165
  };
46763
47166
  }
47167
+ /**
47168
+ * Match on the owning subject, falling back to the session for runs started
47169
+ * before an owner was recorded (and for anonymous/public servers, where every
47170
+ * caller has an empty subject and the session is the only identity there is).
47171
+ *
47172
+ * A record carrying NEITHER identity is refused. Such a run cannot be
47173
+ * attributed to anyone, and its inputs and results are exactly what this check
47174
+ * exists to protect — anyone holding the id would otherwise be able to read
47175
+ * it. Internal callers that must read unattributed runs should go through the
47176
+ * execution manager directly rather than this tool.
47177
+ */
47178
+ ownsRun(record) {
47179
+ const callerSub = resolvePrincipal2(this.authInfo, this.scope.authoritiesContextBuilder).sub;
47180
+ if (record.ownerSub) return record.ownerSub === callerSub;
47181
+ if (record.sessionId) return record.sessionId === this.authInfo.sessionId;
47182
+ return false;
47183
+ }
46764
47184
  };
46765
47185
  GetJobStatusTool = __decorateClass([
46766
47186
  FrontMcpTool({
@@ -46795,6 +47215,7 @@ var init_list_jobs_tool = __esm({
46795
47215
  "use strict";
46796
47216
  import_lazy_zod90 = require("@frontmcp/lazy-zod");
46797
47217
  init_common();
47218
+ init_job_permission_guard();
46798
47219
  ListJobsTool = class extends ToolContext {
46799
47220
  async execute(input) {
46800
47221
  const jobRegistry = this.scope.jobs;
@@ -46805,7 +47226,11 @@ var init_list_jobs_tool = __esm({
46805
47226
  tags: input.tags,
46806
47227
  labels: input.labels
46807
47228
  });
46808
- const mapped = jobs.map((j) => ({
47229
+ const visible = [];
47230
+ for (const job of jobs) {
47231
+ if (await this.maySee(job)) visible.push(job);
47232
+ }
47233
+ const mapped = visible.map((j) => ({
46809
47234
  name: j.name,
46810
47235
  description: j.metadata.description,
46811
47236
  tags: j.getTags(),
@@ -46817,6 +47242,17 @@ var init_list_jobs_tool = __esm({
46817
47242
  count: mapped.length
46818
47243
  };
46819
47244
  }
47245
+ /**
47246
+ * A job is listable when the caller passes its `list` rules and is not
47247
+ * blocked from executing it. Checking `execute` too keeps the listing honest:
47248
+ * showing a job the caller can never run is the disclosure this closes.
47249
+ */
47250
+ async maySee(job) {
47251
+ const permissions = job.metadata.permissions;
47252
+ const builder = this.scope.authoritiesContextBuilder;
47253
+ if (!await JobPermissionGuard.check(permissions, "list", this.authInfo, builder)) return false;
47254
+ return JobPermissionGuard.check(permissions, "execute", this.authInfo, builder);
47255
+ }
46820
47256
  };
46821
47257
  ListJobsTool = __decorateClass([
46822
47258
  FrontMcpTool({
@@ -46852,6 +47288,7 @@ var init_register_job_tool = __esm({
46852
47288
  "use strict";
46853
47289
  import_lazy_zod91 = require("@frontmcp/lazy-zod");
46854
47290
  init_common();
47291
+ init_errors();
46855
47292
  RegisterJobTool = class extends ToolContext {
46856
47293
  async execute(input) {
46857
47294
  const scope = this.scope;
@@ -46859,6 +47296,9 @@ var init_register_job_tool = __esm({
46859
47296
  if (!jobRegistry) {
46860
47297
  return this.fail(new Error("Jobs system is not enabled"));
46861
47298
  }
47299
+ if (!this.dynamicRegistrationEnabled()) {
47300
+ return this.fail(new DynamicJobRegistrationDisabledError("job"));
47301
+ }
46862
47302
  const existing = jobRegistry.findByName(input.name);
46863
47303
  if (existing) {
46864
47304
  return this.fail(new Error(`Job "${input.name}" already exists`));
@@ -46881,6 +47321,10 @@ var init_register_job_tool = __esm({
46881
47321
  });
46882
47322
  return { success: true, jobId };
46883
47323
  }
47324
+ dynamicRegistrationEnabled() {
47325
+ const jobs = this.scope.metadata?.jobs;
47326
+ return jobs?.allowDynamicRegistration === true;
47327
+ }
46884
47328
  };
46885
47329
  RegisterJobTool = __decorateClass([
46886
47330
  FrontMcpTool({
@@ -46911,6 +47355,8 @@ var init_remove_job_tool = __esm({
46911
47355
  "use strict";
46912
47356
  import_lazy_zod92 = require("@frontmcp/lazy-zod");
46913
47357
  init_common();
47358
+ init_errors();
47359
+ init_job_permission_guard();
46914
47360
  RemoveJobTool = class extends ToolContext {
46915
47361
  async execute(input) {
46916
47362
  const scope = this.scope;
@@ -46920,7 +47366,16 @@ var init_remove_job_tool = __esm({
46920
47366
  }
46921
47367
  const job = jobRegistry.findByName(input.name);
46922
47368
  if (!job) {
46923
- return this.fail(new Error(`Job "${input.name}" not found`));
47369
+ return this.fail(new JobNotAuthorizedError(input.name));
47370
+ }
47371
+ const allowed = await JobPermissionGuard.check(
47372
+ job.metadata.permissions,
47373
+ "delete",
47374
+ this.authInfo,
47375
+ this.scope.authoritiesContextBuilder
47376
+ );
47377
+ if (!allowed) {
47378
+ return this.fail(new JobNotAuthorizedError(input.name));
46924
47379
  }
46925
47380
  if (!job.isDynamic()) {
46926
47381
  return this.fail(new Error(`Job "${input.name}" is not a dynamic job and cannot be removed`));
@@ -46981,14 +47436,18 @@ async function registerJobCapabilities(args) {
46981
47436
  ListJobsTool,
46982
47437
  ExecuteJobTool,
46983
47438
  GetJobStatusTool,
46984
- RegisterJobTool,
46985
47439
  RemoveJobTool,
46986
47440
  ListWorkflowsTool,
46987
47441
  ExecuteWorkflowTool,
46988
47442
  GetWorkflowStatusTool,
46989
- RegisterWorkflowTool,
46990
47443
  RemoveWorkflowTool
46991
47444
  ];
47445
+ if (jobsConfig.allowDynamicRegistration === true) {
47446
+ managementTools.push(RegisterJobTool, RegisterWorkflowTool);
47447
+ logger.warn(
47448
+ "Dynamic job registration is ENABLED: register_job/register_workflow accept a raw script and register it as an executable job."
47449
+ );
47450
+ }
46992
47451
  logger.info(
46993
47452
  `Jobs initialized: ${jobRegistry.getJobs().length} jobs, ${workflowRegistry.getWorkflows().length} workflows`
46994
47453
  );
@@ -51820,6 +52279,7 @@ var init_utils2 = __esm({
51820
52279
  init_caller_dir_utils();
51821
52280
  init_decide_request_intent_utils();
51822
52281
  init_path_utils();
52282
+ init_principal_utils();
51823
52283
  init_global_config_utils();
51824
52284
  init_primitive_filter();
51825
52285
  init_validate_remote_url();
@@ -52767,9 +53227,24 @@ var init_redis_event_store = __esm({
52767
53227
  await client.expire(streamKey, this.ttlSeconds);
52768
53228
  return `${streamId}:${redisId}`;
52769
53229
  }
53230
+ /**
53231
+ * Resolve the stream an event belongs to.
53232
+ *
53233
+ * Optional in the upstream `EventStore` contract, and the transport's
53234
+ * 409-conflict guard (refusing a second live stream for the same id) is dead
53235
+ * code while no store provides it (GHSA-84j6-jc92-77jm).
53236
+ */
53237
+ async getStreamIdForEventId(eventId) {
53238
+ const parsed = this.parseEventId(eventId);
53239
+ return parsed ? parsed[0] : void 0;
53240
+ }
52770
53241
  async replayEventsAfter(lastEventId, { send }) {
53242
+ const parsed = this.parseEventId(lastEventId);
53243
+ if (!parsed) {
53244
+ return "default-stream";
53245
+ }
52771
53246
  const client = await this.getClient();
52772
- const [streamId, redisId] = this.parseEventId(lastEventId);
53247
+ const [streamId, redisId] = parsed;
52773
53248
  const streamKey = `${this.keyPrefix}${streamId}`;
52774
53249
  const entries = await client.xrange(streamKey, `(${redisId}`, "+");
52775
53250
  for (const [id, fields] of entries) {
@@ -52788,16 +53263,32 @@ var init_redis_event_store = __esm({
52788
53263
  * Event IDs have format: `{streamId}:{redisStreamEntryId}`
52789
53264
  * Redis stream entry IDs have format: `{timestamp}-{sequence}`
52790
53265
  *
53266
+ * The entry-id half is validated because it is interpolated into the `xrange`
53267
+ * range argument (`(${redisId}`), where a malformed value is a protocol error
53268
+ * rather than data.
53269
+ *
53270
+ * The stream-id half is NOT character-filtered. `xadd`/`xrange` take the key
53271
+ * as an exact argument — Redis applies no glob matching there — and ioredis
53272
+ * length-prefixes it, so no character is special. Filtering here would also
53273
+ * make `storeEvent`, which accepts any `StreamId`, able to mint ids this
53274
+ * function then refuses, silently breaking replay for that stream. Ownership
53275
+ * is enforced by the session-scoped facade (GHSA-84j6-jc92-77jm), not by
53276
+ * guessing at the id's characters.
53277
+ *
53278
+ * @returns the pair, or `undefined` when the id is malformed.
53279
+ *
52791
53280
  * @example
52792
53281
  * 'my-stream:1234567890123-0' -> ['my-stream', '1234567890123-0']
52793
53282
  */
52794
53283
  parseEventId(eventId) {
52795
- const lastColonBeforeDash = eventId.lastIndexOf(":", eventId.lastIndexOf("-"));
52796
- if (lastColonBeforeDash === -1) {
52797
- const lastColon = eventId.lastIndexOf(":");
52798
- return [eventId.slice(0, lastColon), eventId.slice(lastColon + 1)];
52799
- }
52800
- return [eventId.slice(0, lastColonBeforeDash), eventId.slice(lastColonBeforeDash + 1)];
53284
+ const value = String(eventId);
53285
+ const lastDash = value.lastIndexOf("-");
53286
+ const separator = lastDash === -1 ? value.lastIndexOf(":") : value.lastIndexOf(":", lastDash);
53287
+ if (separator <= 0 || separator === value.length - 1) return void 0;
53288
+ const streamId = value.slice(0, separator);
53289
+ const redisId = value.slice(separator + 1);
53290
+ if (!/^\d+(-\d+)?$/.test(redisId)) return void 0;
53291
+ return [streamId, redisId];
52801
53292
  }
52802
53293
  /**
52803
53294
  * Close the Redis client connection.
@@ -52843,6 +53334,16 @@ var init_memory_event_store = __esm({
52843
53334
  }
52844
53335
  return id;
52845
53336
  }
53337
+ /**
53338
+ * Resolve the stream an event belongs to.
53339
+ *
53340
+ * Optional in the upstream `EventStore` contract, and the transport's
53341
+ * 409-conflict guard (refusing a second live stream for the same id) is dead
53342
+ * code while no store provides it (GHSA-84j6-jc92-77jm).
53343
+ */
53344
+ async getStreamIdForEventId(eventId) {
53345
+ return this.index.get(eventId)?.streamId;
53346
+ }
52846
53347
  async replayEventsAfter(lastEventId, { send }) {
52847
53348
  const meta = this.index.get(lastEventId);
52848
53349
  if (!meta) {
@@ -52994,6 +53495,50 @@ var init_event_store_factory = __esm({
52994
53495
  }
52995
53496
  });
52996
53497
 
53498
+ // libs/sdk/src/transport/event-stores/session-scoped.event-store.ts
53499
+ function createSessionScopedEventStore(store, sessionId) {
53500
+ const prefix = `${sessionId}${SCOPE_SEPARATOR}`;
53501
+ const scope = (streamId) => `${prefix}${streamId}`;
53502
+ const unscope = (streamId) => {
53503
+ const value = String(streamId);
53504
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
53505
+ };
53506
+ const resolveOwnedStream = async (eventId) => {
53507
+ const lookup = store.getStreamIdForEventId;
53508
+ if (lookup) {
53509
+ const resolved = await lookup.call(store, eventId);
53510
+ return resolved !== void 0 && String(resolved).startsWith(prefix) ? resolved : void 0;
53511
+ }
53512
+ if (!String(eventId).startsWith(prefix)) return void 0;
53513
+ const value = String(eventId);
53514
+ const lastColon = value.lastIndexOf(":");
53515
+ return lastColon === -1 ? void 0 : value.slice(0, lastColon);
53516
+ };
53517
+ return {
53518
+ async storeEvent(streamId, message) {
53519
+ return store.storeEvent(scope(streamId), message);
53520
+ },
53521
+ async getStreamIdForEventId(eventId) {
53522
+ const owned = await resolveOwnedStream(eventId);
53523
+ return owned === void 0 ? void 0 : unscope(owned);
53524
+ },
53525
+ async replayEventsAfter(lastEventId, options) {
53526
+ const owned = await resolveOwnedStream(lastEventId);
53527
+ if (owned === void 0) {
53528
+ return "default-stream";
53529
+ }
53530
+ return unscope(await store.replayEventsAfter(lastEventId, options));
53531
+ }
53532
+ };
53533
+ }
53534
+ var SCOPE_SEPARATOR;
53535
+ var init_session_scoped_event_store = __esm({
53536
+ "libs/sdk/src/transport/event-stores/session-scoped.event-store.ts"() {
53537
+ "use strict";
53538
+ SCOPE_SEPARATOR = " ";
53539
+ }
53540
+ });
53541
+
52997
53542
  // libs/sdk/src/transport/event-stores/index.ts
52998
53543
  var init_event_stores = __esm({
52999
53544
  "libs/sdk/src/transport/event-stores/index.ts"() {
@@ -54664,6 +55209,61 @@ var init_transport_error = __esm({
54664
55209
  }
54665
55210
  });
54666
55211
 
55212
+ // libs/sdk/src/server/security/host-validation.ts
55213
+ function normalizeHost(value) {
55214
+ const host = value.trim().toLowerCase();
55215
+ if (host.length === 0) return host;
55216
+ const portSeparator = host.startsWith("[") ? host.indexOf(":", host.indexOf("]")) : host.lastIndexOf(":");
55217
+ if (portSeparator === -1) return host;
55218
+ const name42 = host.slice(0, portSeparator);
55219
+ const port = host.slice(portSeparator + 1);
55220
+ if (port.length === 0 || !/^\d+$/.test(port)) return host;
55221
+ return DEFAULT_PORTS.has(port) ? name42 : `${name42}:${port}`;
55222
+ }
55223
+ function normalizeOrigin(value) {
55224
+ const origin = value.trim().toLowerCase();
55225
+ try {
55226
+ return new URL(origin).origin.toLowerCase();
55227
+ } catch {
55228
+ return origin;
55229
+ }
55230
+ }
55231
+ function compileHostValidation(rules) {
55232
+ return {
55233
+ hosts: rules.allowedHosts ? new Set(rules.allowedHosts.map(normalizeHost)) : void 0,
55234
+ origins: rules.allowedOrigins ? new Set(rules.allowedOrigins.map(normalizeOrigin)) : void 0
55235
+ };
55236
+ }
55237
+ function validateHostHeaders(headers, compiled) {
55238
+ if (compiled.hosts) {
55239
+ const host = headers.host;
55240
+ if (!host || !compiled.hosts.has(normalizeHost(host))) {
55241
+ return { status: 403, error: "Forbidden", message: "Invalid Host header" };
55242
+ }
55243
+ const forwardedHost = headers.forwardedHost;
55244
+ if (forwardedHost) {
55245
+ const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
55246
+ if (candidates.some((h2) => !compiled.hosts?.has(normalizeHost(h2)))) {
55247
+ return { status: 403, error: "Forbidden", message: "Invalid X-Forwarded-Host header" };
55248
+ }
55249
+ }
55250
+ }
55251
+ if (compiled.origins) {
55252
+ const origin = headers.origin;
55253
+ if (origin && !compiled.origins.has(normalizeOrigin(origin))) {
55254
+ return { status: 403, error: "Forbidden", message: "Invalid Origin header" };
55255
+ }
55256
+ }
55257
+ return void 0;
55258
+ }
55259
+ var DEFAULT_PORTS;
55260
+ var init_host_validation = __esm({
55261
+ "libs/sdk/src/server/security/host-validation.ts"() {
55262
+ "use strict";
55263
+ DEFAULT_PORTS = /* @__PURE__ */ new Set(["80", "443"]);
55264
+ }
55265
+ });
55266
+
54667
55267
  // libs/sdk/src/transport/adapters/base-sse-transport.ts
54668
55268
  var import_content_type, import_raw_body, import_protocol54, import_utils93, MAXIMUM_MESSAGE_SIZE, SSEServerTransport;
54669
55269
  var init_base_sse_transport = __esm({
@@ -54674,6 +55274,7 @@ var init_base_sse_transport = __esm({
54674
55274
  import_protocol54 = require("@frontmcp/protocol");
54675
55275
  import_utils93 = require("@frontmcp/utils");
54676
55276
  init_transport_errors();
55277
+ init_host_validation();
54677
55278
  MAXIMUM_MESSAGE_SIZE = "4mb";
54678
55279
  SSEServerTransport = class {
54679
55280
  /**
@@ -54697,25 +55298,31 @@ var init_base_sse_transport = __esm({
54697
55298
  onmessage;
54698
55299
  /**
54699
55300
  * Validates request headers for DNS rebinding protection.
55301
+ *
55302
+ * Since v1.7.2 the primary defence is the adapter-level Host validation, which
55303
+ * runs before routing and therefore already covers `/sse` and `/message`
55304
+ * (GHSA-mc9g-v2cp-vfff). These per-transport options remain for a host that
55305
+ * constructs the transport directly; they delegate to the same rules so the
55306
+ * two can never disagree about what a valid Host looks like.
55307
+ *
54700
55308
  * @returns Error message if validation fails, undefined if validation passes.
54701
55309
  */
54702
55310
  validateRequestHeaders(req) {
54703
55311
  if (!this._options.enableDnsRebindingProtection) {
54704
55312
  return void 0;
54705
55313
  }
54706
- if (this._options.allowedHosts && this._options.allowedHosts.length > 0) {
54707
- const hostHeader = req.headers.host;
54708
- if (!hostHeader || !this._options.allowedHosts.includes(hostHeader)) {
54709
- return `Invalid Host header: ${hostHeader}`;
54710
- }
54711
- }
54712
- if (this._options.allowedOrigins && this._options.allowedOrigins.length > 0) {
54713
- const originHeader = req.headers.origin;
54714
- if (!originHeader || !this._options.allowedOrigins.includes(originHeader)) {
54715
- return `Invalid Origin header: ${originHeader}`;
54716
- }
54717
- }
54718
- return void 0;
55314
+ const rejection = validateHostHeaders(
55315
+ {
55316
+ host: req.headers.host,
55317
+ forwardedHost: req.headers["x-forwarded-host"],
55318
+ origin: req.headers.origin
55319
+ },
55320
+ compileHostValidation({
55321
+ allowedHosts: this._options.allowedHosts?.length ? this._options.allowedHosts : void 0,
55322
+ allowedOrigins: this._options.allowedOrigins?.length ? this._options.allowedOrigins : void 0
55323
+ })
55324
+ );
55325
+ return rejection?.message;
54719
55326
  }
54720
55327
  /**
54721
55328
  * Handles the initial SSE connection request.
@@ -55213,7 +55820,7 @@ var init_transport_local_adapter = __esm({
55213
55820
  }
55214
55821
  }
55215
55822
  ensureAuthInfo(req, transport) {
55216
- const { token, user, session } = req[ServerRequestTokens.auth];
55823
+ const { token, user, session, scopes, claims } = req[ServerRequestTokens.auth];
55217
55824
  if (!session?.id) {
55218
55825
  throw new UnauthorizedError(
55219
55826
  "Session could not be reconstructed for the provided token. Re-authenticate to start a new session.",
@@ -55242,7 +55849,16 @@ var init_transport_local_adapter = __esm({
55242
55849
  user,
55243
55850
  sessionId,
55244
55851
  sessionIdPayload: sessionPayload,
55245
- scopes: [],
55852
+ // Carry the scopes and claims the verified Authorization actually holds.
55853
+ // These used to be dropped (`scopes: []`, no claims), which left every
55854
+ // scope- or claim-based check downstream inert.
55855
+ //
55856
+ // Both are COPIED, not aliased. Tool code receives this object, and a
55857
+ // nested mutation (`authInfo.claims.roles.push('admin')`) on a shared
55858
+ // reference would rewrite the request's own verified authorization — which
55859
+ // the job/workflow permission guard then reads.
55860
+ scopes: Array.isArray(scopes) ? [...scopes] : [],
55861
+ ...claims ? { claims: structuredClone(claims) } : {},
55246
55862
  clientId: user.sub ?? "",
55247
55863
  transport
55248
55864
  };
@@ -55723,6 +56339,7 @@ var init_transport_streamable_http_adapter = __esm({
55723
56339
  "use strict";
55724
56340
  import_lazy_zod105 = require("@frontmcp/lazy-zod");
55725
56341
  init_errors();
56342
+ init_session_scoped_event_store();
55726
56343
  init_transport_error();
55727
56344
  init_streamable_http_transport();
55728
56345
  init_transport_local_adapter();
@@ -55732,7 +56349,8 @@ var init_transport_streamable_http_adapter = __esm({
55732
56349
  TransportStreamableHttpAdapter = class extends LocalTransportAdapter {
55733
56350
  createTransport(sessionId, response) {
55734
56351
  const sessionIdGenerator = resolveSessionIdGenerator(this.key.type, sessionId);
55735
- const eventStore = this.scope.eventStore;
56352
+ const sharedEventStore = this.scope.eventStore;
56353
+ const eventStore = sharedEventStore ? createSessionScopedEventStore(sharedEventStore, sessionId) : void 0;
55736
56354
  const isStateless = this.key.type === "stateless-http";
55737
56355
  return new RecreateableStreamableHTTPServerTransport({
55738
56356
  sessionIdGenerator,
@@ -56789,11 +57407,11 @@ function parseUserScopes(user) {
56789
57407
  const s = user?.scope;
56790
57408
  return typeof s === "string" ? s.split(/\s+/).filter(Boolean) : [];
56791
57409
  }
56792
- var import_auth38, import_lazy_zod106, import_utils96, plan40, httpRequestStateSchema, name41, Stage41, HttpRequestFlow;
57410
+ var import_auth39, import_lazy_zod106, import_utils96, plan40, httpRequestStateSchema, name41, Stage41, HttpRequestFlow;
56793
57411
  var init_http_request_flow = __esm({
56794
57412
  "libs/sdk/src/scope/flows/http.request.flow.ts"() {
56795
57413
  "use strict";
56796
- import_auth38 = require("@frontmcp/auth");
57414
+ import_auth39 = require("@frontmcp/auth");
56797
57415
  import_lazy_zod106 = require("@frontmcp/lazy-zod");
56798
57416
  import_utils96 = require("@frontmcp/utils");
56799
57417
  init_session_verify_flow();
@@ -56987,20 +57605,20 @@ var init_http_request_flow = __esm({
56987
57605
  const tokenStore = auth.orchestratedTokenStore;
56988
57606
  let providers;
56989
57607
  try {
56990
- const authorizationId = (0, import_auth38.deriveAuthorizationId)(token);
57608
+ const authorizationId = (0, import_auth39.deriveAuthorizationId)(token);
56991
57609
  const providerIds = await tokenStore.getProviderIds(authorizationId);
56992
57610
  providers = Object.fromEntries(providerIds.map((id) => [id, { id }]));
56993
57611
  } catch (err) {
56994
57612
  this.logger.warn(`[${this.requestId}] failed to load orchestrated providers: ${String(err)}`);
56995
57613
  }
56996
- const authorization = import_auth38.OrchestratedAuthorization.create({
57614
+ const authorization = import_auth39.OrchestratedAuthorization.create({
56997
57615
  token,
56998
57616
  user: { sub: user.sub ?? "unknown", name: user.name, email: user.email, picture: user.picture },
56999
57617
  expiresAt: user.exp ? user.exp * 1e3 : void 0,
57000
57618
  tokenStore,
57001
57619
  providers
57002
57620
  });
57003
- ctx.setContextToken(import_auth38.ORCHESTRATED_AUTH_ACCESSOR, new import_auth38.OrchestratedAuthAccessorAdapter(authorization));
57621
+ ctx.setContextToken(import_auth39.ORCHESTRATED_AUTH_ACCESSOR, new import_auth39.OrchestratedAuthAccessorAdapter(authorization));
57004
57622
  }
57005
57623
  async router() {
57006
57624
  try {
@@ -59611,7 +60229,13 @@ function normalizeAppScope(appItem, metadata) {
59611
60229
  ...metadata,
59612
60230
  id: scopeId,
59613
60231
  apps: [appItem],
59614
- auth: appMetadata.auth,
60232
+ // An app that declares no `auth` INHERITS the server's policy. Writing
60233
+ // `appMetadata.auth` unconditionally set the key to `undefined`, which
60234
+ // overrode the spread above and dropped the server's auth entirely — so a
60235
+ // standalone app silently became public (GHSA-rgxj-434m-vxh3, where the
60236
+ // dashboard's own scope was the case in point). Declaring
60237
+ // `auth: { mode: 'public' }` explicitly is still honoured.
60238
+ auth: appMetadata.auth ?? metadata.auth,
59615
60239
  // For a splitByApp scope, auth (and any auth.ui paths) come from `@App`, so
59616
60240
  // anchor on the App's captured source dir. Fall back to the server's.
59617
60241
  // `__sourceDir` lives on LocalAppMetadata only; read it defensively.
@@ -59875,6 +60499,11 @@ function createWebFetchHandler(scope, options = {}) {
59875
60499
  const entryPaths = new Set((Array.isArray(rawEntry) ? rawEntry : [rawEntry]).map(normalizePath));
59876
60500
  const cors2 = options.cors ?? mapHttpCors(httpConfig?.cors, scope);
59877
60501
  const corsEnabled = cors2?.origin !== void 0 && cors2.origin !== false;
60502
+ const rebinding = httpConfig?.security?.dnsRebindingProtection;
60503
+ const hostValidation = rebinding?.enabled === false || !rebinding?.allowedHosts?.length && !rebinding?.allowedOrigins?.length ? void 0 : compileHostValidation({
60504
+ allowedHosts: rebinding.allowedHosts,
60505
+ allowedOrigins: rebinding.allowedOrigins
60506
+ });
59878
60507
  const corsHeadersFor = (request) => {
59879
60508
  if (!corsEnabled) return {};
59880
60509
  const reqOrigin = request.headers.get("origin") ?? "";
@@ -59905,6 +60534,19 @@ function createWebFetchHandler(scope, options = {}) {
59905
60534
  };
59906
60535
  return async function handle(request, ctx, env) {
59907
60536
  const url = new URL(request.url);
60537
+ if (hostValidation) {
60538
+ const rejection = validateHostHeaders(
60539
+ {
60540
+ host: request.headers.get("host") ?? url.host,
60541
+ forwardedHost: request.headers.get("x-forwarded-host") ?? void 0,
60542
+ origin: request.headers.get("origin") ?? void 0
60543
+ },
60544
+ hostValidation
60545
+ );
60546
+ if (rejection) {
60547
+ return Response.json({ error: rejection.error, message: rejection.message }, { status: rejection.status });
60548
+ }
60549
+ }
59908
60550
  if (corsEnabled && request.method === "OPTIONS") {
59909
60551
  return new Response(null, { status: 204, headers: corsHeadersFor(request) });
59910
60552
  }
@@ -60038,6 +60680,7 @@ var init_web_fetch_handler = __esm({
60038
60680
  init_server_tokens();
60039
60681
  init_path_utils();
60040
60682
  init_errors();
60683
+ init_host_validation();
60041
60684
  init_web_response_renderer();
60042
60685
  }
60043
60686
  });
@@ -60190,39 +60833,22 @@ function createHostValidationMiddleware(options) {
60190
60833
  if (!options.enabled) {
60191
60834
  return (_req, _res, next) => next();
60192
60835
  }
60193
- const allowedHostsSet = options.allowedHosts ? new Set(options.allowedHosts) : void 0;
60194
- const allowedOriginsSet = options.allowedOrigins ? new Set(options.allowedOrigins) : void 0;
60836
+ const compiled = compileHostValidation({
60837
+ allowedHosts: options.allowedHosts,
60838
+ allowedOrigins: options.allowedOrigins
60839
+ });
60195
60840
  return (req, res, next) => {
60196
- if (allowedHostsSet) {
60197
- const host = req.headers?.["host"];
60198
- if (!host || !allowedHostsSet.has(host)) {
60199
- res.status(403).json({
60200
- error: "Forbidden",
60201
- message: "Invalid Host header"
60202
- });
60203
- return;
60204
- }
60205
- const forwardedHost = req.headers?.["x-forwarded-host"];
60206
- if (forwardedHost) {
60207
- const candidates = forwardedHost.split(",").map((h2) => h2.trim()).filter(Boolean);
60208
- if (candidates.some((h2) => !allowedHostsSet.has(h2))) {
60209
- res.status(403).json({
60210
- error: "Forbidden",
60211
- message: "Invalid X-Forwarded-Host header"
60212
- });
60213
- return;
60214
- }
60215
- }
60216
- }
60217
- if (allowedOriginsSet) {
60218
- const origin = req.headers?.["origin"];
60219
- if (origin && !allowedOriginsSet.has(origin)) {
60220
- res.status(403).json({
60221
- error: "Forbidden",
60222
- message: "Invalid Origin header"
60223
- });
60224
- return;
60225
- }
60841
+ const rejection = validateHostHeaders(
60842
+ {
60843
+ host: req.headers?.["host"],
60844
+ forwardedHost: req.headers?.["x-forwarded-host"],
60845
+ origin: req.headers?.["origin"]
60846
+ },
60847
+ compiled
60848
+ );
60849
+ if (rejection) {
60850
+ res.status(rejection.status).json({ error: rejection.error, message: rejection.message });
60851
+ return;
60226
60852
  }
60227
60853
  next();
60228
60854
  };
@@ -60230,6 +60856,82 @@ function createHostValidationMiddleware(options) {
60230
60856
  var init_host_validation_middleware = __esm({
60231
60857
  "libs/sdk/src/server/middleware/host-validation.middleware.ts"() {
60232
60858
  "use strict";
60859
+ init_host_validation();
60860
+ }
60861
+ });
60862
+
60863
+ // libs/sdk/src/server/security/resolve-allowed-hosts.ts
60864
+ function canonicalizeAddress(address) {
60865
+ const bare = address.trim().toLowerCase().replace(/^\[|\]$/g, "");
60866
+ if (!bare.includes(":")) return bare;
60867
+ try {
60868
+ return new URL(`http://[${bare}]`).hostname.replace(/^\[|\]$/g, "");
60869
+ } catch {
60870
+ return bare;
60871
+ }
60872
+ }
60873
+ function isLoopbackAddress(address) {
60874
+ if (!address) return false;
60875
+ const normalized = canonicalizeAddress(address);
60876
+ return normalized === "::1" || normalized === "localhost" || normalized.startsWith("127.") || // IPv4-mapped IPv6 loopback, which `URL` renders as `::ffff:7f00:1`.
60877
+ normalized === "::ffff:7f00:1" || normalized.startsWith("::ffff:127.");
60878
+ }
60879
+ function withAndWithoutPort(host, port) {
60880
+ return port ? [host, `${host}:${port}`] : [host];
60881
+ }
60882
+ function hostFromUrl(url) {
60883
+ if (!url) return void 0;
60884
+ try {
60885
+ return new URL(url).host || void 0;
60886
+ } catch {
60887
+ return void 0;
60888
+ }
60889
+ }
60890
+ function isRoutableBind(address) {
60891
+ if (!address) return false;
60892
+ return !isLoopbackAddress(address);
60893
+ }
60894
+ function deriveAllowedHosts(input) {
60895
+ const hosts = /* @__PURE__ */ new Set();
60896
+ if (input.socketPath) return [];
60897
+ for (const name42 of LOOPBACK_NAMES) {
60898
+ for (const value of withAndWithoutPort(name42, input.port)) hosts.add(value);
60899
+ }
60900
+ const bindAddress = input.bindAddress?.trim();
60901
+ if (bindAddress && !isLoopbackAddress(bindAddress) && bindAddress !== "0.0.0.0" && bindAddress !== "::") {
60902
+ const literal = bindAddress.includes(":") && !bindAddress.startsWith("[") ? `[${bindAddress}]` : bindAddress;
60903
+ for (const value of withAndWithoutPort(literal, input.port)) hosts.add(value);
60904
+ }
60905
+ const issuerHost = hostFromUrl(input.issuer);
60906
+ if (issuerHost) {
60907
+ hosts.add(issuerHost);
60908
+ const [name42] = issuerHost.split(":");
60909
+ if (!issuerHost.includes(":") && input.port) hosts.add(`${name42}:${input.port}`);
60910
+ }
60911
+ for (const extra of input.extraHosts ?? []) {
60912
+ const trimmed = extra.trim();
60913
+ if (trimmed) hosts.add(trimmed);
60914
+ }
60915
+ return [...hosts];
60916
+ }
60917
+ function shouldEnforceDerivedHosts(input) {
60918
+ if (input.socketPath) return false;
60919
+ const hasExplicitName = hostFromUrl(input.issuer) !== void 0 || (input.extraHosts ?? []).some((h2) => h2.trim());
60920
+ if (hasExplicitName) return true;
60921
+ if (input.port === void 0) return false;
60922
+ return !isRoutableBind(input.bindAddress);
60923
+ }
60924
+ function allowedHostsFromEnv(env = process.env) {
60925
+ const raw = env["FRONTMCP_ALLOWED_HOSTS"]?.trim();
60926
+ if (!raw) return void 0;
60927
+ const hosts = raw.split(",").map((h2) => h2.trim()).filter(Boolean);
60928
+ return hosts.length > 0 ? hosts : void 0;
60929
+ }
60930
+ var LOOPBACK_NAMES;
60931
+ var init_resolve_allowed_hosts = __esm({
60932
+ "libs/sdk/src/server/security/resolve-allowed-hosts.ts"() {
60933
+ "use strict";
60934
+ LOOPBACK_NAMES = ["localhost", "127.0.0.1", "[::1]"];
60233
60935
  }
60234
60936
  });
60235
60937
 
@@ -60255,12 +60957,17 @@ var init_express_host_adapter = __esm({
60255
60957
  import_utils98 = require("@frontmcp/utils");
60256
60958
  init_mcp_error();
60257
60959
  init_host_validation_middleware();
60960
+ init_resolve_allowed_hosts();
60258
60961
  init_base_host_adapter();
60259
60962
  DEFAULT_EXPRESS_BODY_LIMIT = "4mb";
60260
60963
  ExpressHostAdapter = class extends HostServerAdapter {
60261
60964
  app = (0, import_express.default)();
60262
60965
  router = import_express.default.Router();
60263
60966
  prepared = false;
60967
+ /** Active host-validation middleware, or undefined while nothing is enforced. */
60968
+ hostValidation;
60969
+ /** Builds the derived allow-list, once this process commits to listening. */
60970
+ deriveHostValidation;
60264
60971
  constructor(options) {
60265
60972
  super();
60266
60973
  const corsOptions = options?.cors;
@@ -60274,6 +60981,7 @@ var init_express_host_adapter = __esm({
60274
60981
  })
60275
60982
  );
60276
60983
  }
60984
+ this.installHostValidation(options);
60277
60985
  const jsonLimit = options?.bodyLimit ?? DEFAULT_EXPRESS_BODY_LIMIT;
60278
60986
  const formLimit = options?.urlencodedLimit ?? jsonLimit;
60279
60987
  const headerContains = (req, needle) => (req.headers["content-type"] ?? "").toLowerCase().includes(needle);
@@ -60294,23 +61002,6 @@ var init_express_host_adapter = __esm({
60294
61002
  }
60295
61003
  next(err);
60296
61004
  });
60297
- const securityOpts = options?.security;
60298
- if (securityOpts?.dnsRebindingProtection?.enabled || securityOpts?.strict) {
60299
- const allowedHosts = securityOpts.dnsRebindingProtection?.allowedHosts;
60300
- const allowedOrigins = securityOpts.dnsRebindingProtection?.allowedOrigins;
60301
- const effectiveHosts = allowedHosts ?? (securityOpts.strict ? ["localhost", "127.0.0.1"] : void 0);
60302
- if (!effectiveHosts?.length && !allowedOrigins?.length) {
60303
- throw new Error(
60304
- "security.dnsRebindingProtection is enabled but no allowedHosts or allowedOrigins are configured. Provide at least one allowedHosts entry or disable dnsRebindingProtection."
60305
- );
60306
- }
60307
- const hostValidation = createHostValidationMiddleware({
60308
- enabled: true,
60309
- allowedHosts: effectiveHosts,
60310
- allowedOrigins
60311
- });
60312
- this.app.use(hostValidation);
60313
- }
60314
61005
  this.app.use((req, res, next) => {
60315
61006
  if (corsEnabled) {
60316
61007
  res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate, Mcp-Session-Id");
@@ -60320,6 +61011,64 @@ var init_express_host_adapter = __esm({
60320
61011
  next();
60321
61012
  });
60322
61013
  }
61014
+ /**
61015
+ * Install DNS-rebinding protection.
61016
+ *
61017
+ * ON by default since v1.7.2 (BC-035). `allowedHosts` falls back to the names
61018
+ * this process actually answers to — the loopback aliases with and without
61019
+ * the bound port, the bound NIC address, and the configured issuer host.
61020
+ *
61021
+ * Escape hatches, in precedence order: explicit
61022
+ * `security.dnsRebindingProtection.allowedHosts`, the
61023
+ * `FRONTMCP_ALLOWED_HOSTS` env var, or `enabled: false` to turn it off.
61024
+ */
61025
+ installHostValidation(options) {
61026
+ const protection = options?.security?.dnsRebindingProtection;
61027
+ if (protection?.enabled === false) return;
61028
+ const allowedOrigins = protection?.allowedOrigins;
61029
+ const explicitHosts = protection?.allowedHosts ?? allowedHostsFromEnv();
61030
+ if (explicitHosts?.length || allowedOrigins?.length) {
61031
+ this.hostValidation = createHostValidationMiddleware({
61032
+ enabled: true,
61033
+ allowedHosts: explicitHosts?.length ? explicitHosts : void 0,
61034
+ allowedOrigins
61035
+ });
61036
+ } else {
61037
+ this.deriveHostValidation = (portOrSocketPath, bindAddress) => {
61038
+ const isSocket = typeof portOrSocketPath === "string";
61039
+ const derivation = {
61040
+ // `start()` binds `bindAddress ?? '0.0.0.0'`; mirror that exactly.
61041
+ bindAddress: isSocket ? void 0 : bindAddress ?? "0.0.0.0",
61042
+ port: isSocket ? void 0 : portOrSocketPath,
61043
+ socketPath: isSocket ? portOrSocketPath : void 0,
61044
+ issuer: options?.listen?.issuer
61045
+ };
61046
+ if (!shouldEnforceDerivedHosts(derivation)) {
61047
+ console.warn(
61048
+ `[frontmcp] DNS-rebinding protection is not enforcing a Host allow-list: the server binds a routable address (${derivation.bindAddress ?? "unknown"}) and no allowed hosts are configured. Set security.dnsRebindingProtection.allowedHosts (or FRONTMCP_ALLOWED_HOSTS) to your public hostname(s).`
61049
+ );
61050
+ return void 0;
61051
+ }
61052
+ const allowedHosts = deriveAllowedHosts(derivation);
61053
+ if (!allowedHosts.length) return void 0;
61054
+ return createHostValidationMiddleware({ enabled: true, allowedHosts });
61055
+ };
61056
+ }
61057
+ this.app.use(((req, res, next) => {
61058
+ const validate = this.hostValidation;
61059
+ if (!validate) return next();
61060
+ return validate(req, res, next);
61061
+ }));
61062
+ }
61063
+ /**
61064
+ * Enable the DERIVED host allow-list. Called from `start()` only — see
61065
+ * `installHostValidation`.
61066
+ */
61067
+ enableDerivedHostValidation(portOrSocketPath, bindAddress) {
61068
+ if (this.hostValidation || !this.deriveHostValidation) return;
61069
+ this.hostValidation = this.deriveHostValidation(portOrSocketPath, bindAddress);
61070
+ this.deriveHostValidation = void 0;
61071
+ }
60323
61072
  registerRoute(method, path3, handler) {
60324
61073
  this.router[method.toLowerCase()](path3, this.enhancedHandler(handler));
60325
61074
  }
@@ -60352,6 +61101,7 @@ var init_express_host_adapter = __esm({
60352
61101
  return this.app;
60353
61102
  }
60354
61103
  async start(portOrSocketPath, bindAddress) {
61104
+ this.enableDerivedHostValidation(portOrSocketPath, bindAddress);
60355
61105
  this.prepare();
60356
61106
  const server = http.createServer(this.app);
60357
61107
  server.requestTimeout = 0;
@@ -63241,6 +63991,7 @@ __export(index_exports, {
63241
63991
  DynamicAdapter: () => DynamicAdapter,
63242
63992
  DynamicAdapterNameError: () => DynamicAdapterNameError,
63243
63993
  DynamicJobDirectExecutionError: () => DynamicJobDirectExecutionError,
63994
+ DynamicJobRegistrationDisabledError: () => DynamicJobRegistrationDisabledError,
63244
63995
  DynamicPlugin: () => DynamicPlugin,
63245
63996
  ELICITATION_FALLBACK_JSON_SCHEMA: () => ELICITATION_FALLBACK_JSON_SCHEMA,
63246
63997
  ESM_SH_BASE_URL: () => ESM_SH_BASE_URL,
@@ -63364,7 +64115,7 @@ __export(index_exports, {
63364
64115
  HttpTextSchema: () => HttpTextSchema,
63365
64116
  HttpWebResponseSchema: () => HttpWebResponseSchema,
63366
64117
  ImageOutputSchema: () => ImageOutputSchema,
63367
- InMemoryOrchestratedTokenStore: () => import_auth40.InMemoryOrchestratedTokenStore,
64118
+ InMemoryOrchestratedTokenStore: () => import_auth41.InMemoryOrchestratedTokenStore,
63368
64119
  InMemoryStoreRequiredError: () => import_auth11.InMemoryStoreRequiredError,
63369
64120
  InProcessTaskRunner: () => InProcessTaskRunner,
63370
64121
  InputRequiredSignal: () => InputRequiredSignal,
@@ -63395,6 +64146,7 @@ __export(index_exports, {
63395
64146
  JobExecutionManager: () => JobExecutionManager,
63396
64147
  JobInstance: () => JobInstance,
63397
64148
  JobKind: () => JobKind,
64149
+ JobNotAuthorizedError: () => JobNotAuthorizedError,
63398
64150
  JobPermissionGuard: () => JobPermissionGuard,
63399
64151
  JobRegistry: () => JobRegistry,
63400
64152
  LLM_ADAPTER: () => LLM_ADAPTER,
@@ -63912,6 +64664,7 @@ __export(index_exports, {
63912
64664
  resolveNameConflicts: () => resolveNameConflicts,
63913
64665
  resolveOutputSchemaMode: () => resolveOutputSchemaMode,
63914
64666
  resolvePendingTC39HooksForClass: () => resolvePendingTC39HooksForClass,
64667
+ resolvePrincipal: () => resolvePrincipal2,
63915
64668
  resolveRegistryToken: () => resolveRegistryToken,
63916
64669
  resolveSchemaDescriptionFormat: () => resolveSchemaDescriptionFormat,
63917
64670
  resolveToolVisibility: () => resolveToolVisibility,
@@ -64004,49 +64757,7 @@ init_job_registry();
64004
64757
  init_job_instance();
64005
64758
  init_job_events();
64006
64759
  init_job_utils();
64007
-
64008
- // libs/sdk/src/job/job-permission.guard.ts
64009
- var JobPermissionGuard = class _JobPermissionGuard {
64010
- /**
64011
- * Check if the given auth info is allowed to perform the action.
64012
- * Returns true if no permissions are defined (permissive by default).
64013
- */
64014
- static async check(permissions, action, authInfo) {
64015
- if (!permissions || permissions.length === 0) {
64016
- return true;
64017
- }
64018
- const relevant = permissions.filter((p) => p.action === action);
64019
- if (relevant.length === 0) {
64020
- return true;
64021
- }
64022
- for (const perm of relevant) {
64023
- const passed = await _JobPermissionGuard.checkSingle(perm, authInfo);
64024
- if (!passed) return false;
64025
- }
64026
- return true;
64027
- }
64028
- static async checkSingle(perm, authInfo) {
64029
- if (perm.roles && perm.roles.length > 0) {
64030
- const userRoles = authInfo["roles"];
64031
- if (!Array.isArray(userRoles)) return false;
64032
- const hasRole = perm.roles.some((r) => userRoles.includes(r));
64033
- if (!hasRole) return false;
64034
- }
64035
- if (perm.scopes && perm.scopes.length > 0) {
64036
- const userScopes = authInfo["scopes"] ?? authInfo["scope"];
64037
- const scopeArr = typeof userScopes === "string" ? userScopes.split(" ") : Array.isArray(userScopes) ? userScopes : [];
64038
- const hasScope = perm.scopes.some((s) => scopeArr.includes(s));
64039
- if (!hasScope) return false;
64040
- }
64041
- if (perm.custom) {
64042
- const result = await perm.custom(authInfo);
64043
- if (!result) return false;
64044
- }
64045
- return true;
64046
- }
64047
- };
64048
-
64049
- // libs/sdk/src/job/index.ts
64760
+ init_job_permission_guard();
64050
64761
  init_job_scope_helper();
64051
64762
 
64052
64763
  // libs/sdk/src/job/execution/index.ts
@@ -64342,7 +65053,7 @@ init_mcp_20260728();
64342
65053
  init_mcp_stateless_client_adapter();
64343
65054
  var import_protocol57 = require("@frontmcp/protocol");
64344
65055
  init_ext_apps2();
64345
- var import_auth40 = require("@frontmcp/auth");
65056
+ var import_auth41 = require("@frontmcp/auth");
64346
65057
  (function suppressZodExpressWarning() {
64347
65058
  if (typeof process === "undefined" || typeof process.emitWarning !== "function") return;
64348
65059
  const originalEmitWarning = process.emitWarning.bind(process);
@@ -64449,6 +65160,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
64449
65160
  DynamicAdapter,
64450
65161
  DynamicAdapterNameError,
64451
65162
  DynamicJobDirectExecutionError,
65163
+ DynamicJobRegistrationDisabledError,
64452
65164
  DynamicPlugin,
64453
65165
  ELICITATION_FALLBACK_JSON_SCHEMA,
64454
65166
  ESM_SH_BASE_URL,
@@ -64603,6 +65315,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
64603
65315
  JobExecutionManager,
64604
65316
  JobInstance,
64605
65317
  JobKind,
65318
+ JobNotAuthorizedError,
64606
65319
  JobPermissionGuard,
64607
65320
  JobRegistry,
64608
65321
  LLM_ADAPTER,
@@ -65119,6 +65832,7 @@ var ChannelListHook = FlowHooksOf("channels:list");
65119
65832
  resolveNameConflicts,
65120
65833
  resolveOutputSchemaMode,
65121
65834
  resolvePendingTC39HooksForClass,
65835
+ resolvePrincipal,
65122
65836
  resolveRegistryToken,
65123
65837
  resolveSchemaDescriptionFormat,
65124
65838
  resolveToolVisibility,