@agent-native/core 0.201.0 → 0.201.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/a2a/handlers.js +96 -44
  2. package/dist/a2a/organization-identity.d.ts +9 -0
  3. package/dist/a2a/organization-identity.js +34 -0
  4. package/dist/a2a/server.d.ts +13 -7
  5. package/dist/a2a/server.js +187 -88
  6. package/dist/a2a/task-store.d.ts +7 -2
  7. package/dist/a2a/task-store.js +23 -8
  8. package/dist/a2a-claims.js +12 -2
  9. package/dist/app-config/mcp.d.ts +4 -0
  10. package/dist/app-config/mcp.js +7 -0
  11. package/dist/app-config/schema.d.ts +3 -0
  12. package/dist/app-config/schema.js +2 -0
  13. package/dist/automation/index.d.ts +1 -1
  14. package/dist/client/route-chunk-recovery.js +75 -11
  15. package/dist/client/route-warmup.d.ts +2 -0
  16. package/dist/client/route-warmup.js +3 -0
  17. package/dist/client/vite-dev-recovery-script.js +85 -17
  18. package/dist/collab/awareness.d.ts +2 -2
  19. package/dist/collab/struct-routes.d.ts +1 -1
  20. package/dist/deploy/build.js +3 -1
  21. package/dist/feature-flags/a2a-action-route.js +3 -4
  22. package/dist/mcp/build-server.d.ts +4 -0
  23. package/dist/mcp/build-server.js +152 -43
  24. package/dist/mcp/embed-app.js +5 -5
  25. package/dist/mcp/server.d.ts +0 -18
  26. package/dist/mcp/server.js +99 -9
  27. package/dist/onboarding/app-profile.js +2 -1
  28. package/dist/onboarding/workspace-services.js +3 -0
  29. package/dist/org/context.d.ts +13 -0
  30. package/dist/org/context.js +60 -0
  31. package/dist/org/handlers.d.ts +1 -1
  32. package/dist/org/index.d.ts +1 -1
  33. package/dist/org/membership.d.ts +6 -0
  34. package/dist/org/membership.js +23 -5
  35. package/dist/progress/routes.d.ts +1 -1
  36. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  37. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  38. package/dist/secrets/routes.d.ts +11 -11
  39. package/dist/server/action-routes.js +12 -5
  40. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  41. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  42. package/dist/server/builder-browser.d.ts +4 -2
  43. package/dist/server/builder-browser.js +147 -9
  44. package/dist/server/core-routes-plugin.js +13 -6
  45. package/dist/server/entry-server.js +43 -2
  46. package/dist/server/sentry.js +9 -1
  47. package/dist/server/ssr-handler.d.ts +1 -1
  48. package/dist/server/ssr-handler.js +3 -7
  49. package/dist/shared/cache-control.d.ts +4 -1
  50. package/dist/shared/cache-control.js +11 -6
  51. package/dist/shared/route-chunk-recovery-bootstrap.d.ts +7 -0
  52. package/dist/shared/route-chunk-recovery-bootstrap.js +38 -0
  53. package/package.json +5 -3
@@ -2,3 +2,9 @@ export declare function isMissingOrganizationTableError(error: unknown): boolean
2
2
  export declare function isOrgMember(orgId: string, email: string, options?: {
3
3
  requireOrganizationMetadata?: boolean;
4
4
  }): Promise<boolean>;
5
+ /**
6
+ * Checks membership for an A2A identity authenticated by an organization
7
+ * secret. Unlike legacy callers, this requires readable organization metadata
8
+ * as well as the membership row before the token subject can become an owner.
9
+ */
10
+ export declare function isOrgMemberForA2A(orgId: string, email: string): Promise<boolean>;
@@ -17,7 +17,7 @@ export function isMissingOrganizationTableError(error) {
17
17
  }
18
18
  return false;
19
19
  }
20
- export async function isOrgMember(orgId, email, options = {}) {
20
+ async function isOrgMemberWithPolicy(orgId, email, options) {
21
21
  const normalized = email.trim().toLowerCase();
22
22
  if (!orgId || !normalized)
23
23
  return false;
@@ -41,13 +41,14 @@ export async function isOrgMember(orgId, email, options = {}) {
41
41
  })).rows;
42
42
  }
43
43
  catch (error) {
44
- if (options.requireOrganizationMetadata ||
45
- !isMissingOrganizationTableError(error))
44
+ if (!isMissingOrganizationTableError(error))
46
45
  throw error;
47
- return true;
46
+ if (options.allowMissingOrganizationTable)
47
+ return true;
48
+ throw error;
48
49
  }
49
50
  const organization = organizationRows[0];
50
- if (!organization && options.requireOrganizationMetadata)
51
+ if (!organization && options.requireOrganizationRecord)
51
52
  return false;
52
53
  const linked = String(organization?.identity_authority ?? "").trim() ||
53
54
  String(organization?.identity_id ?? "").trim();
@@ -67,3 +68,20 @@ export async function isOrgMember(orgId, email, options = {}) {
67
68
  });
68
69
  return validation.active;
69
70
  }
71
+ export function isOrgMember(orgId, email, options = {}) {
72
+ return isOrgMemberWithPolicy(orgId, email, {
73
+ allowMissingOrganizationTable: !options.requireOrganizationMetadata,
74
+ requireOrganizationRecord: !!options.requireOrganizationMetadata,
75
+ });
76
+ }
77
+ /**
78
+ * Checks membership for an A2A identity authenticated by an organization
79
+ * secret. Unlike legacy callers, this requires readable organization metadata
80
+ * as well as the membership row before the token subject can become an owner.
81
+ */
82
+ export function isOrgMemberForA2A(orgId, email) {
83
+ return isOrgMemberWithPolicy(orgId, email, {
84
+ allowMissingOrganizationTable: false,
85
+ requireOrganizationRecord: true,
86
+ });
87
+ }
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -75,9 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
+ message?: undefined;
78
79
  id?: undefined;
79
- deleted?: undefined;
80
80
  provider?: undefined;
81
+ deleted?: undefined;
81
82
  providers: {
82
83
  id: string;
83
84
  label: string;
@@ -91,8 +92,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
91
92
  found?: undefined;
92
93
  registered?: undefined;
93
94
  label?: undefined;
94
- message?: undefined;
95
95
  } | {
96
+ message?: undefined;
96
97
  id?: undefined;
97
98
  deleted?: undefined;
98
99
  providers?: undefined;
@@ -101,18 +102,18 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
101
102
  provider: import("../custom-registry.js").CustomProviderConfig;
102
103
  registered?: undefined;
103
104
  label?: undefined;
104
- message?: undefined;
105
105
  } | {
106
- deleted?: undefined;
106
+ message?: undefined;
107
107
  provider?: undefined;
108
+ deleted?: undefined;
108
109
  providers?: undefined;
109
110
  count?: undefined;
110
111
  found: boolean;
111
112
  id: string;
112
113
  registered?: undefined;
113
114
  label?: undefined;
114
- message?: undefined;
115
115
  } | {
116
+ message?: undefined;
116
117
  provider?: undefined;
117
118
  providers?: undefined;
118
119
  count?: undefined;
@@ -121,10 +122,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
121
122
  id: string;
122
123
  registered?: undefined;
123
124
  label?: undefined;
124
- message?: undefined;
125
125
  } | {
126
- deleted?: undefined;
127
126
  provider?: undefined;
127
+ deleted?: undefined;
128
128
  providers?: undefined;
129
129
  count?: undefined;
130
130
  found?: undefined;
@@ -311,10 +311,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
311
311
  notes?: string | undefined;
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
- provider?: undefined;
314
+ id?: undefined;
315
315
  deleted?: undefined;
316
316
  message?: undefined;
317
- id?: undefined;
318
317
  providers: {
319
318
  id: string;
320
319
  label: string;
@@ -325,44 +324,45 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
325
324
  updatedAt: number;
326
325
  }[];
327
326
  count: number;
327
+ provider?: undefined;
328
328
  found?: undefined;
329
329
  registered?: undefined;
330
330
  label?: undefined;
331
331
  } | {
332
+ id?: undefined;
332
333
  count?: undefined;
333
334
  deleted?: undefined;
334
335
  message?: undefined;
335
- id?: undefined;
336
336
  providers?: undefined;
337
337
  found: boolean;
338
338
  provider: import("../custom-registry.js").CustomProviderConfig;
339
339
  registered?: undefined;
340
340
  label?: undefined;
341
341
  } | {
342
- provider?: undefined;
343
342
  count?: undefined;
344
343
  deleted?: undefined;
345
344
  message?: undefined;
346
345
  providers?: undefined;
346
+ provider?: undefined;
347
347
  found: boolean;
348
348
  id: string;
349
349
  registered?: undefined;
350
350
  label?: undefined;
351
351
  } | {
352
- provider?: undefined;
353
352
  count?: undefined;
354
353
  message?: undefined;
355
354
  providers?: undefined;
355
+ provider?: undefined;
356
356
  found?: undefined;
357
357
  deleted: boolean;
358
358
  id: string;
359
359
  registered?: undefined;
360
360
  label?: undefined;
361
361
  } | {
362
- provider?: undefined;
363
362
  count?: undefined;
364
363
  deleted?: undefined;
365
364
  providers?: undefined;
365
+ provider?: undefined;
366
366
  found?: undefined;
367
367
  registered: boolean;
368
368
  id: string;
@@ -58,47 +58,47 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
58
58
  error: string;
59
59
  errorCode?: undefined;
60
60
  retryable?: undefined;
61
- ok?: undefined;
62
61
  status?: undefined;
62
+ ok?: undefined;
63
63
  } | {
64
64
  error: string;
65
65
  errorCode: string;
66
66
  retryable?: undefined;
67
- ok?: undefined;
68
67
  status?: undefined;
68
+ ok?: undefined;
69
69
  } | {
70
70
  error: string;
71
71
  errorCode: string;
72
72
  retryable: boolean;
73
- ok?: undefined;
74
73
  status?: undefined;
74
+ ok?: undefined;
75
75
  } | {
76
- error?: undefined;
77
76
  errorCode?: undefined;
78
77
  retryable?: undefined;
79
78
  ok: boolean;
80
79
  status: string;
80
+ error?: undefined;
81
81
  } | {
82
- ok?: undefined;
83
82
  error: string;
84
83
  removed?: undefined;
84
+ ok?: undefined;
85
85
  } | {
86
- error?: undefined;
87
86
  ok: boolean;
88
87
  removed: boolean;
88
+ error?: undefined;
89
89
  }>>;
90
90
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
91
- ok?: undefined;
92
91
  error: string;
93
92
  note?: undefined;
93
+ ok?: undefined;
94
94
  } | {
95
- error?: undefined;
96
95
  ok: boolean;
97
96
  note?: undefined;
98
- } | {
99
97
  error?: undefined;
98
+ } | {
100
99
  ok: boolean;
101
100
  note: string;
101
+ error?: undefined;
102
102
  } | {
103
103
  note?: undefined;
104
104
  ok: boolean;
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
126
126
  } | {
127
127
  error: string;
128
128
  } | {
129
- error?: undefined;
130
129
  ok: boolean;
131
130
  key: string;
132
- } | {
133
131
  error?: undefined;
132
+ } | {
134
133
  ok: boolean;
135
134
  removed: boolean;
135
+ error?: undefined;
136
136
  }>>;
@@ -1,13 +1,14 @@
1
1
  import { createError, defineEventHandler, setResponseStatus, setResponseHeader, getMethod, getQuery, getHeader, readBody as readH3Body, } from "h3";
2
2
  import "../authorization/check-action.js";
3
3
  import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
4
+ import { isA2AIdentityVerificationUnavailableError } from "../a2a/server.js";
4
5
  import { actionCallEmitsChange, actionChangeResource, } from "../action-call-classification.js";
5
6
  import { ActionContractError, isActionContractError, isActionExposedToExternalAgents, isAgentActionStopError, validateActionArgs, } from "../action.js";
6
7
  import { LLM_CREDENTIAL_KEYS, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
7
8
  import { isTransientDatabaseError } from "../db/client.js";
8
9
  import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
9
10
  import { isFeatureFlagAdminEmail } from "../feature-flags/permissions.js";
10
- import { isFederationMembershipValidatedForEvent, resolveOrgByDomain, resolveOrgIdForEmail, } from "../org/context.js";
11
+ import { isFederationMembershipValidatedForEvent, resolveOrgIdForEmail, } from "../org/context.js";
11
12
  import { LLM_PROVIDER_MISSING_ERROR_CODE, LLM_PROVIDER_MISSING_STATUS, } from "../shared/action-error-codes.js";
12
13
  import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
13
14
  import { EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
@@ -66,8 +67,8 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
66
67
  const claims = await verifyA2ATokenWithClaims(token, event);
67
68
  if (!claims || !claims.scope.includes(required))
68
69
  throw new Error("Invalid feature flag delegation");
69
- const localOrg = await resolveOrgByDomain(claims.orgDomain);
70
- if (!localOrg && !isFeatureFlagAdminEmail(claims.email))
70
+ const orgId = claims.orgId?.trim() || null;
71
+ if (!orgId && !isFeatureFlagAdminEmail(claims.email))
71
72
  throw new Error("Invalid feature flag delegation");
72
73
  if (actionName === "set-feature-flag" &&
73
74
  (await consumeOneTimeJti(claims.jti))) {
@@ -75,7 +76,7 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
75
76
  }
76
77
  return {
77
78
  owner: claims.email,
78
- orgId: localOrg?.orgId ?? null,
79
+ orgId,
79
80
  anonymous: false,
80
81
  delegationJti: claims.jti,
81
82
  delegationIssuer: claims.issuer,
@@ -386,7 +387,13 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
386
387
  if (!caller)
387
388
  caller = await resolveFeatureFlagA2ACaller(event, name);
388
389
  }
389
- catch {
390
+ catch (error) {
391
+ if (isA2AIdentityVerificationUnavailableError(error)) {
392
+ throw createError({
393
+ statusCode: 503,
394
+ statusMessage: "Identity verification temporarily unavailable",
395
+ });
396
+ }
390
397
  throw createError({
391
398
  statusCode: 401,
392
399
  statusMessage: "Unauthorized",
@@ -65,6 +65,7 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
65
65
  error: any;
66
66
  code: any;
67
67
  } | {
68
+ error?: undefined;
68
69
  errorCode?: undefined;
69
70
  code?: undefined;
70
71
  ok: boolean;
@@ -72,6 +73,5 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
72
73
  baseUrlKey?: string;
73
74
  scope: AgentEngineApiKeyScope;
74
75
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
75
- error?: undefined;
76
76
  }>>;
77
77
  export {};
@@ -8,8 +8,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
8
8
  * resolveEngine's credential detection.
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- error: string;
12
11
  ok?: undefined;
12
+ error: string;
13
13
  scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
@@ -43,8 +43,8 @@ export declare class BuilderAccountProvisioningError extends Error {
43
43
  export declare function isBuilderAccountAlreadyExistsError(error: unknown): boolean;
44
44
  export declare function createBuilderConnectState(): string;
45
45
  export declare function isSignedBuilderConnectState(value: string | null | undefined): boolean;
46
- export declare function isBuilderConnectCallbackUrlAllowed(candidate: string, event: H3Event): boolean;
47
- export declare function resolveBuilderConnectCallbackUrl(event: H3Event, state?: string): string | null;
46
+ export declare function isBuilderConnectCallbackUrlAllowed(candidate: string, event: H3Event, callbackOriginOverride?: string): boolean;
47
+ export declare function resolveBuilderConnectCallbackUrl(event: H3Event, state?: string, callbackOriginOverride?: string): string | null;
48
48
  export declare function isSafeBuilderRelayTargetOrigin(value: string): boolean;
49
49
  export declare function isTrustedBuilderRelayTargetOrigin(value: string): boolean;
50
50
  export declare function resolveBuilderPreviewRelayTargetOrigin(previewOrigin: string): string;
@@ -94,6 +94,7 @@ export declare const BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM = "agentNativeCon
94
94
  export declare const BUILDER_AGENT_NATIVE_APP_PARAM = "agentNativeApp";
95
95
  export declare const BUILDER_AGENT_NATIVE_TEMPLATE_PARAM = "agentNativeTemplate";
96
96
  export declare const BUILDER_CONNECT_MODE_PARAM = "_an_mode";
97
+ export declare const BUILDER_PREVIEW_ORIGIN_HEADER = "x-agent-native-preview-origin";
97
98
  export declare const BUILDER_AGENT_NATIVE_PROVISION_MODE = "agent-native";
98
99
  export declare const BUILDER_PROVISIONING_TOKEN_PARAM = "_an_provision";
99
100
  export declare const BUILDER_CONNECT_ATTEMPT_PARAM = "_an_connect_attempt";
@@ -207,6 +208,7 @@ export declare function buildBuilderCliAuthUrl(callbackOrigin: string, state?: s
207
208
  }): string;
208
209
  export declare function getBuilderBrowserConnectUrl(origin: string): string;
209
210
  export declare function getBuilderBrowserConnectUrlForOwner(origin: string, ownerEmail: string | null | undefined): string;
211
+ export declare function getBuilderConnectCallbackOriginFromUrl(callbackUrl: string): string | null;
210
212
  export declare function getBuilderBrowserOriginForEvent(event: H3Event): string;
211
213
  export declare function getBuilderCliAuthCallbackOriginForEvent(event: H3Event): string;
212
214
  export declare function getBuilderBrowserStatus(origin: string): BuilderBrowserStatus;
@@ -1,5 +1,5 @@
1
1
  import { createHash, createHmac, randomBytes, timingSafeEqual, } from "node:crypto";
2
- import { getHeader, getRequestIP, setResponseHeader, setResponseStatus, } from "h3";
2
+ import { getHeader, getQuery, getRequestIP, setResponseHeader, setResponseStatus, } from "h3";
3
3
  import { ActionContractError } from "../action.js";
4
4
  import { getSetting } from "../settings/store.js";
5
5
  import { applyBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
@@ -112,8 +112,8 @@ export function isSignedBuilderConnectState(value) {
112
112
  return safeEqualText(expected, signature);
113
113
  });
114
114
  }
115
- export function isBuilderConnectCallbackUrlAllowed(candidate, event) {
116
- const callbackOrigin = getBuilderConnectCallbackOrigin(event);
115
+ export function isBuilderConnectCallbackUrlAllowed(candidate, event, callbackOriginOverride) {
116
+ const callbackOrigin = getBuilderConnectCallbackOrigin(event, callbackOriginOverride);
117
117
  if (!callbackOrigin ||
118
118
  !isAllowedOAuthRedirectUri(candidate, event, callbackOrigin))
119
119
  return false;
@@ -137,16 +137,18 @@ export function isBuilderConnectCallbackUrlAllowed(candidate, event) {
137
137
  }
138
138
  return url.protocol === "https:";
139
139
  }
140
- export function resolveBuilderConnectCallbackUrl(event, state) {
140
+ export function resolveBuilderConnectCallbackUrl(event, state, callbackOriginOverride) {
141
141
  const stateSuffix = state ? `?state=${encodeURIComponent(state)}` : "";
142
- const callbackOrigin = getBuilderConnectCallbackOrigin(event);
142
+ const callbackOrigin = getBuilderConnectCallbackOrigin(event, callbackOriginOverride);
143
143
  if (!callbackOrigin)
144
144
  return null;
145
145
  const withBase = `${callbackOrigin}${getAppBasePath()}${BUILDER_CALLBACK_PATH}${stateSuffix}`;
146
- if (isBuilderConnectCallbackUrlAllowed(withBase, event))
146
+ if (isBuilderConnectCallbackUrlAllowed(withBase, event, callbackOriginOverride)) {
147
147
  return withBase;
148
+ }
148
149
  const root = `${callbackOrigin}${BUILDER_CALLBACK_PATH}${stateSuffix}`;
149
- if (root !== withBase && isBuilderConnectCallbackUrlAllowed(root, event)) {
150
+ if (root !== withBase &&
151
+ isBuilderConnectCallbackUrlAllowed(root, event, callbackOriginOverride)) {
150
152
  return root;
151
153
  }
152
154
  return null;
@@ -429,6 +431,9 @@ export const BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM = "agentNativeConnectSour
429
431
  export const BUILDER_AGENT_NATIVE_APP_PARAM = "agentNativeApp";
430
432
  export const BUILDER_AGENT_NATIVE_TEMPLATE_PARAM = "agentNativeTemplate";
431
433
  export const BUILDER_CONNECT_MODE_PARAM = "_an_mode";
434
+ export const BUILDER_PREVIEW_ORIGIN_HEADER = "x-agent-native-preview-origin";
435
+ const BUILDER_PREVIEW_ORIGIN_PARAM = "_an_preview_origin";
436
+ const BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM = "_an_preview_origin_signature";
432
437
  export const BUILDER_AGENT_NATIVE_PROVISION_MODE = "agent-native";
433
438
  export const BUILDER_PROVISIONING_TOKEN_PARAM = "_an_provision";
434
439
  export const BUILDER_CONNECT_ATTEMPT_PARAM = "_an_connect_attempt";
@@ -958,7 +963,15 @@ function isConfiguredBuilderRequestHost(event, host) {
958
963
  (process.env.NODE_ENV === "production" ? "https" : "http");
959
964
  return isConfiguredAppOrigin(`${proto}://${host}`);
960
965
  }
961
- function getBuilderConnectCallbackOrigin(event) {
966
+ function getBuilderConnectCallbackOrigin(event, originOverride) {
967
+ if (originOverride) {
968
+ return isAllowedBuilderConnectOrigin(originOverride)
969
+ ? originOverride
970
+ : null;
971
+ }
972
+ const signedPreviewOrigin = getSignedBuilderPreviewOriginFromEvent(event);
973
+ if (signedPreviewOrigin)
974
+ return signedPreviewOrigin;
962
975
  const requestHost = firstHeaderValue(readEventHeader(event, "host"));
963
976
  const headerHost = getBuilderRequestHost(event);
964
977
  if (isRejectedDirectBuilderCloudHost(requestHost, headerHost)) {
@@ -978,6 +991,123 @@ function getBuilderConnectCallbackOrigin(event) {
978
991
  }
979
992
  return configuredOrigin;
980
993
  }
994
+ function isAllowedBuilderConnectOrigin(origin) {
995
+ let url;
996
+ try {
997
+ url = new URL(origin);
998
+ }
999
+ catch {
1000
+ // coercion-ok: a malformed callback origin is explicitly rejected.
1001
+ return false;
1002
+ }
1003
+ if (url.origin !== origin ||
1004
+ url.username ||
1005
+ url.password ||
1006
+ url.pathname !== "/" ||
1007
+ url.search ||
1008
+ url.hash) {
1009
+ return false;
1010
+ }
1011
+ if (isTrustedBuilderRequestHost(url.host)) {
1012
+ return (url.protocol === "https:" ||
1013
+ (url.protocol === "http:" && process.env.NODE_ENV !== "production"));
1014
+ }
1015
+ return isConfiguredAppOrigin(origin);
1016
+ }
1017
+ function normalizeBuilderPreviewOrigin(value) {
1018
+ try {
1019
+ const url = new URL(value);
1020
+ if (url.origin !== value ||
1021
+ url.username ||
1022
+ url.password ||
1023
+ url.pathname !== "/" ||
1024
+ url.search ||
1025
+ url.hash ||
1026
+ (url.protocol !== "https:" &&
1027
+ !(url.protocol === "http:" && process.env.NODE_ENV !== "production"))) {
1028
+ return null;
1029
+ }
1030
+ return url.origin;
1031
+ }
1032
+ catch {
1033
+ // coercion-ok: a malformed preview origin is not an allowed origin.
1034
+ return null;
1035
+ }
1036
+ }
1037
+ function getSameOriginBuilderPreviewOrigin(event) {
1038
+ const rawOrigin = firstHeaderValue(readEventHeader(event, BUILDER_PREVIEW_ORIGIN_HEADER));
1039
+ if (!rawOrigin || readEventHeader(event, "sec-fetch-site") === "cross-site") {
1040
+ return null;
1041
+ }
1042
+ const origin = normalizeBuilderPreviewOrigin(rawOrigin);
1043
+ if (!origin || !isTrustedBuilderRequestHost(new URL(origin).host)) {
1044
+ return null;
1045
+ }
1046
+ const requestHost = firstHeaderValue(readEventHeader(event, "host"));
1047
+ if (!requestHost ||
1048
+ (!isLoopbackBuilderRequestHost(requestHost) &&
1049
+ requestHost.toLowerCase() !== new URL(origin).host.toLowerCase())) {
1050
+ return null;
1051
+ }
1052
+ const fetchSite = readEventHeader(event, "sec-fetch-site");
1053
+ const requestOrigin = firstHeaderValue(readEventHeader(event, "origin"));
1054
+ if (fetchSite === "same-origin" || requestOrigin === origin)
1055
+ return origin;
1056
+ const referer = firstHeaderValue(readEventHeader(event, "referer"));
1057
+ if (!referer)
1058
+ return null;
1059
+ try {
1060
+ return new URL(referer).origin === origin ? origin : null;
1061
+ }
1062
+ catch {
1063
+ // coercion-ok: an unreadable Referer cannot authorize a preview origin.
1064
+ return null;
1065
+ }
1066
+ }
1067
+ function signBuilderPreviewOrigin(origin) {
1068
+ return createHmac("sha256", builderConnectStateSigningKeys()[0])
1069
+ .update(`builder-connect-preview-origin\0${origin}`)
1070
+ .digest("base64url");
1071
+ }
1072
+ function getSignedBuilderPreviewOriginFromEvent(event) {
1073
+ const query = getQuery(event);
1074
+ const rawOrigin = query[BUILDER_PREVIEW_ORIGIN_PARAM];
1075
+ const signature = query[BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM];
1076
+ if (typeof rawOrigin !== "string" || typeof signature !== "string") {
1077
+ return null;
1078
+ }
1079
+ const origin = normalizeBuilderPreviewOrigin(rawOrigin);
1080
+ if (!origin || !isTrustedBuilderRequestHost(new URL(origin).host)) {
1081
+ return null;
1082
+ }
1083
+ const requestHost = firstHeaderValue(readEventHeader(event, "host"));
1084
+ if (!requestHost ||
1085
+ (!isLoopbackBuilderRequestHost(requestHost) &&
1086
+ requestHost.toLowerCase() !== new URL(origin).host.toLowerCase())) {
1087
+ return null;
1088
+ }
1089
+ return builderConnectStateSigningKeys().some((key) => safeEqualText(createHmac("sha256", key)
1090
+ .update(`builder-connect-preview-origin\0${origin}`)
1091
+ .digest("base64url"), signature))
1092
+ ? origin
1093
+ : null;
1094
+ }
1095
+ function addSignedBuilderPreviewOrigin(url, origin) {
1096
+ const parsed = new URL(url);
1097
+ parsed.searchParams.set(BUILDER_PREVIEW_ORIGIN_PARAM, origin);
1098
+ parsed.searchParams.set(BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM, signBuilderPreviewOrigin(origin));
1099
+ return parsed.toString();
1100
+ }
1101
+ export function getBuilderConnectCallbackOriginFromUrl(callbackUrl) {
1102
+ try {
1103
+ const origin = new URL(callbackUrl).origin;
1104
+ return isAllowedBuilderConnectOrigin(origin) ? origin : null;
1105
+ }
1106
+ catch {
1107
+ // coercion-ok: a malformed stored callback URL must fail verification.
1108
+ return null;
1109
+ }
1110
+ }
981
1111
  function isRejectedDirectBuilderCloudHost(requestHost, resolvedHost) {
982
1112
  return isBuilderCloudRequestHost(requestHost) && requestHost !== resolvedHost;
983
1113
  }
@@ -986,6 +1116,9 @@ function getConfiguredBuilderFallbackOrigin(event) {
986
1116
  return isConfiguredAppOrigin(origin) ? origin : null;
987
1117
  }
988
1118
  export function getBuilderBrowserOriginForEvent(event) {
1119
+ const signedPreviewOrigin = getSignedBuilderPreviewOriginFromEvent(event);
1120
+ if (signedPreviewOrigin)
1121
+ return signedPreviewOrigin;
989
1122
  const requestHost = firstHeaderValue(readEventHeader(event, "host"));
990
1123
  const headerHost = getBuilderRequestHost(event);
991
1124
  if (isRejectedDirectBuilderCloudHost(requestHost, headerHost)) {
@@ -1057,7 +1190,12 @@ function parseOptionalEnvBoolean(value) {
1057
1190
  return /^(1|true)$/i.test(value);
1058
1191
  }
1059
1192
  export function getBuilderBrowserStatusForEvent(event) {
1060
- return getBuilderBrowserStatus(getBuilderBrowserOriginForEvent(event));
1193
+ const previewOrigin = getSameOriginBuilderPreviewOrigin(event);
1194
+ const status = getBuilderBrowserStatus(previewOrigin ?? getBuilderBrowserOriginForEvent(event));
1195
+ if (previewOrigin && status.connectUrl) {
1196
+ status.connectUrl = addSignedBuilderPreviewOrigin(status.connectUrl, previewOrigin);
1197
+ }
1198
+ return status;
1061
1199
  }
1062
1200
  export const BUILDER_ENV_KEYS = [
1063
1201
  "BUILDER_PRIVATE_KEY",
@@ -59,7 +59,7 @@ import { getSession } from "./auth.js";
59
59
  import { createAutomationFailureUnsubscribeHandler } from "./automation-failure-notifications.js";
60
60
  import { getBetterAuthInternalAdapter, getBetterAuthSync, } from "./better-auth-instance.js";
61
61
  import { resolveBuilderRequestAuthorization } from "./builder-api-auth.js";
62
- import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_MODE_PARAM, BUILDER_AGENT_NATIVE_PROVISION_MODE, BUILDER_CONNECT_ATTEMPT_PARAM, BUILDER_CONNECT_STATE_COOKIE, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, appendBuilderConnectStateCookie, builderConnectTrackingProperties, BUILDER_UPSTREAM_FAILURE_STATUS, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderAccountAlreadyExistsError, isBuilderAccountProvisioningEnabled, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, parseBuilderConnectStateCookie, provisionBuilderAccount, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderConnectCallbackState, resolveBuilderPreviewRelayParentOrigin, removeBuilderConnectStateCookie, runBuilderAgent, sendBuilderPopupErrorPage, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, signBuilderProvisioningToken, verifyBuilderProvisioningToken, withBuilderConnectTrackingParams, } from "./builder-browser.js";
62
+ import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_MODE_PARAM, BUILDER_AGENT_NATIVE_PROVISION_MODE, BUILDER_CONNECT_ATTEMPT_PARAM, BUILDER_CONNECT_STATE_COOKIE, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, appendBuilderConnectStateCookie, builderConnectTrackingProperties, BUILDER_UPSTREAM_FAILURE_STATUS, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectCallbackOriginFromUrl, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderAccountAlreadyExistsError, isBuilderAccountProvisioningEnabled, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, parseBuilderConnectStateCookie, provisionBuilderAccount, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderConnectCallbackState, resolveBuilderPreviewRelayParentOrigin, removeBuilderConnectStateCookie, runBuilderAgent, sendBuilderPopupErrorPage, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, signBuilderProvisioningToken, verifyBuilderProvisioningToken, withBuilderConnectTrackingParams, } from "./builder-browser.js";
63
63
  import { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, builderOAuthScopeFor, canRoleConnectPersonalBuilder, deleteBuilderOAuthSession, exchangeBuilderOAuthAuthorization, getBuilderOAuthGrants, getBuilderOAuthStoredScope, hasStoredBuilderOAuthGrant, isBuilderOrgManagerRole, isPersonalBuilderGrantAllowed, saveBuilderOAuthCredentials, startBuilderOAuthAuthorization, } from "./builder-oauth.js";
64
64
  import { captureError, registerErrorCaptureProvider } from "./capture-error.js";
65
65
  import { createChatGPTSubscriptionOAuthCallbackHandler, createChatGPTSubscriptionOAuthStartHandler, } from "./chatgpt-subscription-oauth.js";
@@ -3472,7 +3472,8 @@ export function createCoreRoutesPlugin(options = {}) {
3472
3472
  ? await selectLiveBuilderConnectStates(cookieStates)
3473
3473
  : cookieStates;
3474
3474
  const { state, resetStateCookie } = resolveBuilderConnectCallbackState(queryState, liveStates ? liveStates.join(",") : rawStateCookie);
3475
- const parentOrigin = getBuilderBrowserOriginForEvent(event);
3475
+ let parentOrigin = getBuilderBrowserOriginForEvent(event);
3476
+ let callbackOriginOverride;
3476
3477
  let callbackAttemptId = requestConnectAttemptId;
3477
3478
  // A finished attempt — succeeded or failed — must not leave its
3478
3479
  // state in the cookie, or the next restart resolves against two
@@ -3492,8 +3493,7 @@ export function createCoreRoutesPlugin(options = {}) {
3492
3493
  }
3493
3494
  setCookie(event, BUILDER_CONNECT_STATE_COOKIE, remaining, {
3494
3495
  httpOnly: true,
3495
- secure: (resolveBuilderConnectCallbackUrl(event, finishedState) ??
3496
- parentOrigin).startsWith("https://"),
3496
+ secure: (resolveBuilderConnectCallbackUrl(event, finishedState, callbackOriginOverride) ?? parentOrigin).startsWith("https://"),
3497
3497
  sameSite: "lax",
3498
3498
  path: "/",
3499
3499
  maxAge: Math.ceil(BUILDER_CONNECT_PENDING_TTL_MS / 1_000),
@@ -3553,7 +3553,14 @@ export function createCoreRoutesPlugin(options = {}) {
3553
3553
  const redirectUri = typeof pending.redirectUri === "string"
3554
3554
  ? pending.redirectUri
3555
3555
  : null;
3556
- const expectedRedirectUri = resolveBuilderConnectCallbackUrl(event, state);
3556
+ const storedCallbackOrigin = redirectUri
3557
+ ? getBuilderConnectCallbackOriginFromUrl(redirectUri)
3558
+ : null;
3559
+ if (storedCallbackOrigin) {
3560
+ callbackOriginOverride = storedCallbackOrigin;
3561
+ parentOrigin = storedCallbackOrigin;
3562
+ }
3563
+ const expectedRedirectUri = resolveBuilderConnectCallbackUrl(event, state, callbackOriginOverride);
3557
3564
  if (!ownerEmail ||
3558
3565
  !isBuilderConnectCallbackOwner(ownerEmail, session?.email) ||
3559
3566
  Date.now() >= expiresAt ||
@@ -3561,7 +3568,7 @@ export function createCoreRoutesPlugin(options = {}) {
3561
3568
  !redirectUri ||
3562
3569
  !expectedRedirectUri ||
3563
3570
  redirectUri !== expectedRedirectUri ||
3564
- !isBuilderConnectCallbackUrlAllowed(redirectUri, event)) {
3571
+ !isBuilderConnectCallbackUrlAllowed(redirectUri, event, callbackOriginOverride)) {
3565
3572
  if (Date.now() >= expiresAt) {
3566
3573
  await deleteSetting(`builder-connect-pending:${state}`).catch(() => false); // coercion-ok: expired pending cleanup is best-effort; callback already failed verification
3567
3574
  }