@sentry/junior 0.80.1 → 0.82.0

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 (32) hide show
  1. package/dist/{agent-hooks-ZGTDOXQY.js → agent-hooks-5ZRILRC3.js} +1 -1
  2. package/dist/app.js +7 -905
  3. package/dist/chat/agent-dispatch/runner.d.ts +1 -1
  4. package/dist/chat/app/services.d.ts +1 -1
  5. package/dist/chat/egress/credentialed.d.ts +57 -0
  6. package/dist/chat/egress/plugin.d.ts +24 -0
  7. package/dist/chat/plugins/credential-hooks.d.ts +2 -0
  8. package/dist/chat/respond.d.ts +1 -1
  9. package/dist/chat/sandbox/{egress-credentials.d.ts → egress/credentials.d.ts} +28 -5
  10. package/dist/chat/sandbox/egress/policy.d.ts +32 -0
  11. package/dist/chat/sandbox/egress/proxy.d.ts +35 -0
  12. package/dist/chat/sandbox/{egress-session.d.ts → egress/session.d.ts} +41 -11
  13. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  14. package/dist/chat/tools/types.d.ts +2 -1
  15. package/dist/{chunk-W2QGQCKG.js → chunk-NC6LR6U4.js} +1062 -14
  16. package/dist/{chunk-Y3YUOEAZ.js → chunk-TR2G37II.js} +14 -5
  17. package/dist/{chunk-RV5RYIJW.js → chunk-UXPG6ZIN.js} +28 -0
  18. package/dist/cli/chat.js +3 -3
  19. package/dist/cli/env.js +7 -1
  20. package/dist/cli/init.js +1 -0
  21. package/dist/cli/main.js +1 -1
  22. package/dist/cli/plugins.js +1 -1
  23. package/dist/handlers/agent-dispatch.d.ts +1 -1
  24. package/dist/handlers/sandbox-egress-proxy.d.ts +2 -2
  25. package/dist/reporting.js +1 -1
  26. package/dist/{runner-JOVPVMIH.js → runner-VRFJLG2M.js} +2 -2
  27. package/package.json +6 -5
  28. package/dist/chat/sandbox/egress-policy.d.ts +0 -15
  29. package/dist/chat/sandbox/egress-proxy.d.ts +0 -19
  30. /package/dist/chat/sandbox/{egress-oidc.d.ts → egress/oidc.d.ts} +0 -0
  31. /package/dist/chat/sandbox/{egress-schemas.d.ts → egress/schemas.d.ts} +0 -0
  32. /package/dist/chat/sandbox/{egress-tracing.d.ts → egress/tracing.d.ts} +0 -0
package/dist/app.js CHANGED
@@ -10,13 +10,13 @@ import {
10
10
  buildDeliveredTurnStatePatch,
11
11
  buildDeterministicTurnId,
12
12
  clearPendingAuth,
13
- clearSandboxEgressCredentialLease,
14
13
  coerceThreadArtifactsState,
15
14
  createConversationMemoryService,
16
15
  createUserTokenStore,
17
16
  deleteMcpAuthSession,
18
17
  deleteSlackMessage,
19
18
  estimateTextTokens,
19
+ executeCredentialedEgressRequest,
20
20
  finalizeMcpAuthorization,
21
21
  formatProviderLabel,
22
22
  generateAssistantReply,
@@ -30,7 +30,6 @@ import {
30
30
  getMcpStoredOAuthCredentials,
31
31
  getPersistedSandboxState,
32
32
  getPersistedThreadState,
33
- getSandboxEgressCredentialLease,
34
33
  getThreadTitleSourceMessage,
35
34
  isAuthResumeRetryableTurnError,
36
35
  isCooperativeTurnYieldError,
@@ -39,12 +38,10 @@ import {
39
38
  isRedundantReactionAckText,
40
39
  isRetryableTurnError,
41
40
  isTurnInputCommitLostError,
42
- issueProviderCredentialLease,
43
41
  listThreadReplies,
44
42
  markConversationMessage,
45
43
  markTurnClosed,
46
44
  markTurnFailed,
47
- matchesSandboxEgressDomain,
48
45
  mergeArtifactsState,
49
46
  normalizeConversationText,
50
47
  normalizeSandboxEgressTracePropagationDomains,
@@ -57,12 +54,10 @@ import {
57
54
  removeReactionFromMessage,
58
55
  renderSlackLegacyAttachmentText,
59
56
  resolveBaseUrl,
57
+ resolvePluginOAuthAccount,
60
58
  resolveSandboxEgressProviderForHost,
61
59
  scheduleSessionCompletedPluginTasks,
62
60
  setConfigDefaults,
63
- setSandboxEgressAuthRequiredSignal,
64
- setSandboxEgressCredentialLease,
65
- setSandboxEgressPermissionDeniedSignal,
66
61
  shouldEmitDevAgentTrace,
67
62
  shouldPropagateSandboxEgressTrace,
68
63
  startActiveTurn,
@@ -71,7 +66,7 @@ import {
71
66
  updateConversationStats,
72
67
  uploadFilesToThread,
73
68
  upsertConversationMessage
74
- } from "./chunk-W2QGQCKG.js";
69
+ } from "./chunk-NC6LR6U4.js";
75
70
  import {
76
71
  CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
77
72
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
@@ -149,7 +144,7 @@ import {
149
144
  setPlugins,
150
145
  validatePlugins,
151
146
  verifySlackDirectCredentialSubject
152
- } from "./chunk-Y3YUOEAZ.js";
147
+ } from "./chunk-TR2G37II.js";
153
148
  import {
154
149
  createPluginLogger,
155
150
  createPluginState
@@ -203,7 +198,6 @@ import {
203
198
  discoverSkills
204
199
  } from "./chunk-WCXVEQPI.js";
205
200
  import {
206
- CredentialUnavailableError,
207
201
  buildOAuthTokenRequest,
208
202
  hasRequiredOAuthScope,
209
203
  parseOAuthTokenResponse,
@@ -3548,194 +3542,6 @@ async function GET3(request, provider, waitUntil, options = {}) {
3548
3542
  // src/handlers/oauth-callback.ts
3549
3543
  import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
3550
3544
 
3551
- // src/chat/plugins/credential-hooks.ts
3552
- import {
3553
- pluginAuthorizationSchema,
3554
- pluginCredentialResultSchema,
3555
- pluginGrantSchema,
3556
- pluginProviderAccountSchema
3557
- } from "@sentry/junior-plugin-api";
3558
- function parseSchema(schema, value, message) {
3559
- const result = schema.safeParse(value);
3560
- if (!result.success) {
3561
- throw new Error(message);
3562
- }
3563
- return result.data;
3564
- }
3565
- function parseAuthorization(value, pluginName) {
3566
- if (value === void 0) {
3567
- return void 0;
3568
- }
3569
- const authorization = parseSchema(
3570
- pluginAuthorizationSchema,
3571
- value,
3572
- `Plugin "${pluginName}" grant authorization is invalid`
3573
- );
3574
- if (authorization.provider !== pluginName) {
3575
- throw new Error(
3576
- `Plugin "${pluginName}" grant authorization provider must match the issuing plugin`
3577
- );
3578
- }
3579
- return authorization;
3580
- }
3581
- function parseGrant(value, pluginName) {
3582
- return parseSchema(
3583
- pluginGrantSchema,
3584
- value,
3585
- `Plugin "${pluginName}" grantForEgress returned an invalid grant`
3586
- );
3587
- }
3588
- function pluginFor(provider) {
3589
- return getPlugins().find((candidate) => candidate.manifest.name === provider);
3590
- }
3591
- function basePluginContext(plugin) {
3592
- const pluginName = plugin.manifest.name;
3593
- return {
3594
- plugin: { name: pluginName },
3595
- log: createPluginLogger(pluginName),
3596
- db: getDb()
3597
- };
3598
- }
3599
- function parseCredentialResult(value, pluginName) {
3600
- const result = parseSchema(
3601
- pluginCredentialResultSchema,
3602
- value,
3603
- `Plugin "${pluginName}" issueCredential result is invalid`
3604
- );
3605
- if (result.type === "lease") {
3606
- parseAuthorization(result.lease.authorization, pluginName);
3607
- return result;
3608
- }
3609
- if (result.type === "unavailable") {
3610
- return result;
3611
- }
3612
- parseAuthorization(result.authorization, pluginName);
3613
- return result;
3614
- }
3615
- async function selectPluginGrant(input) {
3616
- const plugin = pluginFor(input.provider);
3617
- const hook = plugin?.hooks?.grantForEgress;
3618
- if (!plugin || !hook) {
3619
- return void 0;
3620
- }
3621
- const result = await hook({
3622
- ...basePluginContext(plugin),
3623
- request: {
3624
- ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
3625
- method: input.method,
3626
- url: input.upstreamUrl.toString()
3627
- }
3628
- });
3629
- return result === void 0 ? void 0 : parseGrant(result, plugin.manifest.name);
3630
- }
3631
- async function onPluginEgressResponse(input) {
3632
- const plugin = pluginFor(input.provider);
3633
- const hook = plugin?.hooks?.onEgressResponse;
3634
- if (!plugin || !hook) {
3635
- return {};
3636
- }
3637
- let permissionDenied;
3638
- await hook({
3639
- ...basePluginContext(plugin),
3640
- grant: input.grant,
3641
- permissionDenied(message) {
3642
- const trimmed = message.trim();
3643
- if (!trimmed) {
3644
- throw new Error(
3645
- `Plugin "${plugin.manifest.name}" onEgressResponse permissionDenied message is empty`
3646
- );
3647
- }
3648
- permissionDenied = { message: trimmed };
3649
- },
3650
- request: {
3651
- method: input.method,
3652
- url: input.upstreamUrl.toString()
3653
- },
3654
- response: input.response
3655
- });
3656
- return permissionDenied ? { permissionDenied } : {};
3657
- }
3658
- function hasEgressCredentialHooks(provider) {
3659
- const hooks = pluginFor(provider)?.hooks;
3660
- return Boolean(hooks?.grantForEgress || hooks?.issueCredential);
3661
- }
3662
- async function resolvePluginOAuthAccount(input) {
3663
- const plugin = pluginFor(input.provider);
3664
- const hook = plugin?.hooks?.resolveOAuthAccount;
3665
- if (!plugin || !hook) {
3666
- return void 0;
3667
- }
3668
- const account = await hook({
3669
- ...basePluginContext(plugin),
3670
- tokens: input.tokens
3671
- });
3672
- return account === void 0 ? void 0 : parseSchema(
3673
- pluginProviderAccountSchema,
3674
- account,
3675
- `Plugin "${plugin.manifest.name}" resolveOAuthAccount returned an invalid account`
3676
- );
3677
- }
3678
- async function issuePluginCredential(input) {
3679
- const plugin = pluginFor(input.provider);
3680
- const hook = plugin?.hooks?.issueCredential;
3681
- if (!plugin || !hook) {
3682
- throw new Error(`Plugin "${input.provider}" has no issueCredential hook`);
3683
- }
3684
- const currentUserId = input.actor.type === "user" ? input.actor.userId : void 0;
3685
- const credentialSubjectUserId = input.credentialSubject?.userId;
3686
- const result = await hook({
3687
- ...basePluginContext(plugin),
3688
- actor: input.actor,
3689
- grant: input.grant,
3690
- ...input.credentialSubject ? { credentialSubject: input.credentialSubject } : {},
3691
- tokens: {
3692
- ...currentUserId ? {
3693
- currentUser: {
3694
- userId: currentUserId,
3695
- get: async () => await input.userTokenStore.get(
3696
- currentUserId,
3697
- plugin.manifest.name
3698
- ),
3699
- set: async (tokens) => {
3700
- await input.userTokenStore.set(
3701
- currentUserId,
3702
- plugin.manifest.name,
3703
- tokens
3704
- );
3705
- },
3706
- withRefresh: async (callback) => await input.userTokenStore.withRefresh(
3707
- currentUserId,
3708
- plugin.manifest.name,
3709
- callback
3710
- )
3711
- }
3712
- } : {},
3713
- ...credentialSubjectUserId ? {
3714
- credentialSubject: {
3715
- userId: credentialSubjectUserId,
3716
- get: async () => await input.userTokenStore.get(
3717
- credentialSubjectUserId,
3718
- plugin.manifest.name
3719
- ),
3720
- set: async (tokens) => {
3721
- await input.userTokenStore.set(
3722
- credentialSubjectUserId,
3723
- plugin.manifest.name,
3724
- tokens
3725
- );
3726
- },
3727
- withRefresh: async (callback) => await input.userTokenStore.withRefresh(
3728
- credentialSubjectUserId,
3729
- plugin.manifest.name,
3730
- callback
3731
- )
3732
- }
3733
- } : {}
3734
- }
3735
- });
3736
- return parseCredentialResult(result, plugin.manifest.name);
3737
- }
3738
-
3739
3545
  // src/chat/slack/app-home.ts
3740
3546
  import fs from "fs";
3741
3547
  import path from "path";
@@ -4554,174 +4360,7 @@ async function GET4(request, provider, waitUntil, options = {}) {
4554
4360
  });
4555
4361
  }
4556
4362
 
4557
- // src/chat/sandbox/egress-credentials.ts
4558
- var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
4559
- var SandboxEgressCredentialError = class extends Error {
4560
- authorization;
4561
- grant;
4562
- kind;
4563
- provider;
4564
- constructor(input) {
4565
- super(input.message);
4566
- this.name = "SandboxEgressCredentialError";
4567
- this.authorization = input.authorization;
4568
- this.grant = input.grant;
4569
- this.kind = input.kind;
4570
- this.provider = input.provider;
4571
- }
4572
- };
4573
- function defaultGrantForProvider(input) {
4574
- const access = HTTP_READ_METHODS.has(
4575
- input.method.toUpperCase()
4576
- ) ? "read" : "write";
4577
- return {
4578
- source: "broker",
4579
- grant: {
4580
- name: "default",
4581
- access,
4582
- reason: `sandbox-egress:${input.provider}:${access}`
4583
- }
4584
- };
4585
- }
4586
- function oauthAuthorizationForProvider(provider) {
4587
- const oauth = pluginCatalogRuntime.getOAuthConfig(provider);
4588
- return oauth ? {
4589
- type: "oauth",
4590
- provider,
4591
- ...oauth.scope ? { scope: oauth.scope } : {}
4592
- } : void 0;
4593
- }
4594
- function credentialSubjectFromContext(context) {
4595
- return "subject" in context.credentials && context.credentials.subject ? { type: "user", userId: context.credentials.subject.userId } : void 0;
4596
- }
4597
- function assertLeaseTransformsOwnedByProvider(provider, lease) {
4598
- for (const transform of lease.headerTransforms) {
4599
- if (resolveSandboxEgressProviderForHost(transform.domain) !== provider) {
4600
- throw new Error(
4601
- `Credential lease for ${provider} included header transform for unowned domain ${transform.domain}`
4602
- );
4603
- }
4604
- }
4605
- }
4606
- async function selectSandboxEgressGrant(input) {
4607
- if (!hasEgressCredentialHooks(input.provider)) {
4608
- return defaultGrantForProvider(input);
4609
- }
4610
- const pluginGrant = await selectPluginGrant({
4611
- ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
4612
- provider: input.provider,
4613
- method: input.method,
4614
- upstreamUrl: input.upstreamUrl
4615
- });
4616
- if (!pluginGrant) {
4617
- throw new Error(
4618
- `Plugin "${input.provider}" grantForEgress must return a grant for sandbox egress`
4619
- );
4620
- }
4621
- return { source: "plugin", grant: pluginGrant };
4622
- }
4623
- function authorizationForSandboxEgressGrant(provider, selection) {
4624
- return selection.source === "broker" ? oauthAuthorizationForProvider(provider) : void 0;
4625
- }
4626
- async function sandboxEgressCredentialLease(provider, selection, context) {
4627
- const { grant } = selection;
4628
- const cached = await getSandboxEgressCredentialLease(
4629
- provider,
4630
- grant.name,
4631
- context
4632
- );
4633
- if (cached) {
4634
- if (selection.source === "plugin" && cached.grant.access !== grant.access) {
4635
- throw new Error(
4636
- `Cached credential lease for ${provider}/${grant.name} has ${cached.grant.access} access, but ${grant.access} was selected`
4637
- );
4638
- }
4639
- return {
4640
- ...cached,
4641
- grant
4642
- };
4643
- }
4644
- let lease;
4645
- if (selection.source === "plugin") {
4646
- const credentialSubject = credentialSubjectFromContext(context);
4647
- const pluginResult = await issuePluginCredential({
4648
- provider,
4649
- grant,
4650
- actor: context.credentials.actor,
4651
- ...credentialSubject ? { credentialSubject } : {},
4652
- userTokenStore: createUserTokenStore()
4653
- });
4654
- if (pluginResult.type === "needed") {
4655
- throw new SandboxEgressCredentialError({
4656
- provider,
4657
- grant,
4658
- kind: "auth_required",
4659
- authorization: pluginResult.authorization,
4660
- message: pluginResult.message
4661
- });
4662
- }
4663
- if (pluginResult.type === "unavailable") {
4664
- throw new SandboxEgressCredentialError({
4665
- provider,
4666
- grant,
4667
- kind: "unavailable",
4668
- message: pluginResult.message
4669
- });
4670
- }
4671
- lease = pluginResult.lease;
4672
- } else {
4673
- try {
4674
- lease = await issueProviderCredentialLease({
4675
- context: context.credentials,
4676
- provider,
4677
- reason: grant.reason ?? `sandbox-egress:${provider}:default`
4678
- });
4679
- } catch (error) {
4680
- if (error instanceof CredentialUnavailableError) {
4681
- throw new SandboxEgressCredentialError({
4682
- provider,
4683
- grant,
4684
- kind: "auth_required",
4685
- authorization: authorizationForSandboxEgressGrant(
4686
- provider,
4687
- selection
4688
- ),
4689
- message: error.message
4690
- });
4691
- }
4692
- throw error;
4693
- }
4694
- }
4695
- const headerTransforms = lease.headerTransforms ?? [];
4696
- if (headerTransforms.length === 0) {
4697
- throw new Error(
4698
- `Credential lease for ${provider} did not include header transforms`
4699
- );
4700
- }
4701
- const leaseExpiresAtMs = Date.parse(lease.expiresAt);
4702
- if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
4703
- throw new Error(`Credential lease for ${provider} is expired`);
4704
- }
4705
- const authorization = selection.source === "broker" ? oauthAuthorizationForProvider(provider) : lease.authorization;
4706
- const cachedLease = {
4707
- provider,
4708
- grant,
4709
- ...lease.account ? { account: lease.account } : {},
4710
- ...authorization ? { authorization } : {},
4711
- expiresAt: lease.expiresAt,
4712
- headerTransforms
4713
- };
4714
- assertLeaseTransformsOwnedByProvider(provider, cachedLease);
4715
- await setSandboxEgressCredentialLease(context, cachedLease);
4716
- return cachedLease;
4717
- }
4718
- function hasSandboxEgressLeaseTransformForHost(lease, host) {
4719
- return lease.headerTransforms.some(
4720
- (transform) => matchesSandboxEgressDomain(host, transform.domain)
4721
- );
4722
- }
4723
-
4724
- // src/chat/sandbox/egress-oidc.ts
4363
+ // src/chat/sandbox/egress/oidc.ts
4725
4364
  import {
4726
4365
  createRemoteJWKSet,
4727
4366
  decodeJwt,
@@ -4791,71 +4430,19 @@ async function verifyVercelSandboxOidcToken(token) {
4791
4430
  return verified.payload;
4792
4431
  }
4793
4432
 
4794
- // src/chat/sandbox/egress-proxy.ts
4795
- import { EgressAuthRequired } from "@sentry/junior-plugin-api";
4433
+ // src/chat/sandbox/egress/proxy.ts
4796
4434
  var OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
4797
4435
  var FORWARDED_HOST_HEADER = "vercel-forwarded-host";
4798
4436
  var FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
4799
4437
  var FORWARDED_PORT_HEADER = "vercel-forwarded-port";
4800
4438
  var FORWARDED_PATH_HEADER = "vercel-forwarded-path";
4801
- var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
4802
- "connection",
4803
- "host",
4804
- "keep-alive",
4805
- "proxy-authenticate",
4806
- "proxy-authorization",
4807
- "te",
4808
- "trailer",
4809
- "transfer-encoding",
4810
- "upgrade"
4811
- ]);
4812
- var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
4813
- OIDC_TOKEN_HEADER,
4814
- FORWARDED_HOST_HEADER,
4815
- FORWARDED_SCHEME_HEADER,
4816
- FORWARDED_PORT_HEADER,
4817
- FORWARDED_PATH_HEADER
4818
- ]);
4819
- var TRACE_PROPAGATION_HEADERS = /* @__PURE__ */ new Set([
4820
- "baggage",
4821
- "sentry-trace",
4822
- "traceparent"
4823
- ]);
4824
- var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
4825
- "content-encoding",
4826
- "content-length"
4827
- ]);
4828
- var UPSTREAM_TOKEN_REJECTION_STATUS = 401;
4829
- var UPSTREAM_PERMISSION_REJECTION_STATUS = 403;
4830
- var GRANT_SELECTION_BODY_TEXT_LIMIT_BYTES = 64 * 1024;
4831
- var RESPONSE_BODY_TEXT_LIMIT_BYTES = 64 * 1024;
4832
4439
  function jsonError(message, status) {
4833
4440
  return Response.json({ error: message }, { status });
4834
4441
  }
4835
- function authRequiredResponse(input) {
4836
- return new Response(
4837
- `junior-auth-required provider=${input.provider} grant=${input.grant.name} access=${input.grant.access} 401 unauthorized
4838
- ${input.message}`,
4839
- {
4840
- status: 401,
4841
- headers: {
4842
- "content-type": "text/plain; charset=utf-8",
4843
- "cache-control": "no-store"
4844
- }
4845
- }
4846
- );
4847
- }
4848
- function shouldLogSandboxEgressInfo() {
4849
- const environment = (process.env.SENTRY_ENVIRONMENT ?? process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "").trim().toLowerCase();
4850
- return environment !== "production";
4851
- }
4852
4442
  function egressAttributes(input) {
4853
4443
  return {
4854
4444
  ...input.egressId ? { "app.sandbox.egress_id": input.egressId } : {},
4855
4445
  ...input.provider ? { "app.provider.name": input.provider } : {},
4856
- ...input.grantName ? { "app.grant.name": input.grantName } : {},
4857
- ...input.grantAccess ? { "app.grant.access": input.grantAccess } : {},
4858
- ...input.grantReason ? { "app.grant.reason": input.grantReason } : {},
4859
4446
  ...input.host ? { "server.address": input.host } : {},
4860
4447
  ...input.method ? { "http.request.method": input.method } : {},
4861
4448
  ...input.path ? { "url.path": input.path } : {},
@@ -4898,63 +4485,6 @@ function routingAttributes(request, upstreamUrl) {
4898
4485
  }
4899
4486
  return attributes;
4900
4487
  }
4901
- function displayedUpstreamPath(upstreamUrl) {
4902
- const gitService = upstreamUrl.searchParams.get("service");
4903
- if (upstreamUrl.hostname.toLowerCase() === "github.com" && (gitService === "git-upload-pack" || gitService === "git-receive-pack")) {
4904
- return `${upstreamUrl.pathname}?service=${gitService}`;
4905
- }
4906
- return upstreamUrl.pathname;
4907
- }
4908
- function upstreamPermissionAttributes(provider, upstream) {
4909
- if (provider !== "github") {
4910
- return {};
4911
- }
4912
- return {
4913
- "app.github.accepted_permissions": upstream.headers.get("x-accepted-github-permissions") ?? void 0,
4914
- "app.github.sso": upstream.headers.get("x-github-sso") ?? void 0
4915
- };
4916
- }
4917
- function githubPermissionHeaders(upstream) {
4918
- const acceptedPermissions = upstream.headers.get(
4919
- "x-accepted-github-permissions"
4920
- );
4921
- const sso = upstream.headers.get("x-github-sso");
4922
- return {
4923
- ...acceptedPermissions ? { acceptedPermissions } : {},
4924
- ...sso ? { sso } : {}
4925
- };
4926
- }
4927
- function permissionDeniedMessage(provider, grant) {
4928
- return `${provider} returned HTTP 403 after Junior injected the ${grant.name} grant. Junior forwarded the request; this is not a local runtime block.`;
4929
- }
4930
- function isEgressAuthRequired(error) {
4931
- return error instanceof EgressAuthRequired || error instanceof Error && error.name === "EgressAuthRequired";
4932
- }
4933
- function logSandboxEgressUpstreamRequest(input) {
4934
- if (!shouldLogSandboxEgressInfo()) {
4935
- return;
4936
- }
4937
- logInfo(
4938
- "sandbox_egress_upstream_request",
4939
- {},
4940
- {
4941
- ...egressAttributes({
4942
- egressId: input.egressId,
4943
- grantAccess: input.grantAccess,
4944
- grantName: input.grantName,
4945
- grantReason: input.grantReason,
4946
- host: input.upstreamUrl.hostname,
4947
- method: input.request.method,
4948
- path: input.upstreamUrl.pathname,
4949
- provider: input.provider,
4950
- status: input.upstream.status
4951
- }),
4952
- ...routingAttributes(input.request, input.upstreamUrl),
4953
- "app.sandbox.egress.upstream_ok": input.upstream.ok
4954
- },
4955
- `Sandbox egress ${input.request.method} ${input.upstreamUrl.hostname}${displayedUpstreamPath(input.upstreamUrl)} -> ${input.upstream.status}`
4956
- );
4957
- }
4958
4488
  function normalizeHost(value) {
4959
4489
  const trimmed = value.trim().toLowerCase();
4960
4490
  if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
@@ -5032,123 +4562,6 @@ function buildUpstreamUrl(request) {
5032
4562
  return { ok: false, error: "Invalid forwarded URL" };
5033
4563
  }
5034
4564
  }
5035
- async function requestBodyBytes(request) {
5036
- if (request.method === "GET" || request.method === "HEAD" || request.body === null) {
5037
- return void 0;
5038
- }
5039
- return await request.arrayBuffer();
5040
- }
5041
- function isGrantSelectionBodyVisible(input) {
5042
- return input.provider === "github" && input.upstreamUrl.hostname.toLowerCase() === "api.github.com" && input.upstreamUrl.pathname.toLowerCase().endsWith("/graphql");
5043
- }
5044
- function requestBodyText(body) {
5045
- if (body === void 0 || body.byteLength > GRANT_SELECTION_BODY_TEXT_LIMIT_BYTES) {
5046
- return void 0;
5047
- }
5048
- return new TextDecoder().decode(body);
5049
- }
5050
- function responseContentLength(upstream) {
5051
- const raw = upstream.headers.get("content-length");
5052
- if (!raw) {
5053
- return void 0;
5054
- }
5055
- const parsed = Number(raw);
5056
- return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
5057
- }
5058
- async function responseTextWithinLimit(upstream, maxBytes) {
5059
- const limit = Math.min(
5060
- Math.max(0, Math.floor(maxBytes)),
5061
- RESPONSE_BODY_TEXT_LIMIT_BYTES
5062
- );
5063
- if (limit <= 0) {
5064
- return void 0;
5065
- }
5066
- const contentLength = responseContentLength(upstream);
5067
- if (contentLength !== void 0 && contentLength > limit) {
5068
- return void 0;
5069
- }
5070
- let clone;
5071
- try {
5072
- clone = upstream.clone();
5073
- } catch {
5074
- return void 0;
5075
- }
5076
- const body = clone.body;
5077
- if (!body) {
5078
- return "";
5079
- }
5080
- const reader = body.getReader();
5081
- const chunks = [];
5082
- let bytes = 0;
5083
- try {
5084
- while (true) {
5085
- const { done, value } = await reader.read();
5086
- if (done) {
5087
- break;
5088
- }
5089
- if (!value) {
5090
- continue;
5091
- }
5092
- bytes += value.byteLength;
5093
- if (bytes > limit) {
5094
- await reader.cancel().catch(() => void 0);
5095
- return void 0;
5096
- }
5097
- chunks.push(value);
5098
- }
5099
- } catch {
5100
- await reader.cancel().catch(() => void 0);
5101
- return void 0;
5102
- } finally {
5103
- reader.releaseLock();
5104
- }
5105
- const combined = new Uint8Array(bytes);
5106
- let offset = 0;
5107
- for (const chunk of chunks) {
5108
- combined.set(chunk, offset);
5109
- offset += chunk.byteLength;
5110
- }
5111
- return new TextDecoder().decode(combined);
5112
- }
5113
- function requestHeaders(request, lease, upstreamHost, tracePropagation) {
5114
- const headers = new Headers();
5115
- const mayPropagateTrace = shouldPropagateSandboxEgressTrace(
5116
- upstreamHost,
5117
- tracePropagation
5118
- );
5119
- request.headers.forEach((value, key) => {
5120
- const normalized = key.toLowerCase();
5121
- if (HOP_BY_HOP_HEADERS.has(normalized) || PROXY_ONLY_HEADERS.has(normalized)) {
5122
- return;
5123
- }
5124
- if (TRACE_PROPAGATION_HEADERS.has(normalized) && !mayPropagateTrace) {
5125
- return;
5126
- }
5127
- headers.append(key, value);
5128
- });
5129
- for (const transform of lease.headerTransforms) {
5130
- if (!matchesSandboxEgressDomain(upstreamHost, transform.domain)) {
5131
- continue;
5132
- }
5133
- for (const [key, value] of Object.entries(transform.headers)) {
5134
- if (TRACE_PROPAGATION_HEADERS.has(key.toLowerCase()) && !mayPropagateTrace) {
5135
- continue;
5136
- }
5137
- headers.set(key, value);
5138
- }
5139
- }
5140
- return headers;
5141
- }
5142
- function responseHeaders(upstream) {
5143
- const headers = new Headers();
5144
- upstream.headers.forEach((value, key) => {
5145
- const normalized = key.toLowerCase();
5146
- if (!HOP_BY_HOP_HEADERS.has(normalized) && !DECODED_RESPONSE_HEADERS.has(normalized)) {
5147
- headers.append(key, value);
5148
- }
5149
- });
5150
- return headers;
5151
- }
5152
4565
  function continueSandboxEgressTrace(request, upstreamHost, tracePropagation, callback) {
5153
4566
  const sentryTrace = request.headers.get("sentry-trace") ?? void 0;
5154
4567
  const baggage = request.headers.get("baggage") ?? void 0;
@@ -5167,9 +4580,6 @@ function isSandboxEgressForwardedRequest(request) {
5167
4580
  );
5168
4581
  }
5169
4582
  async function proxySandboxEgressRequest(request, deps = {}) {
5170
- return await proxySandboxEgressRequestImpl(request, deps);
5171
- }
5172
- async function proxySandboxEgressRequestImpl(request, deps) {
5173
4583
  const oidcToken = request.headers.get(OIDC_TOKEN_HEADER)?.trim();
5174
4584
  if (!oidcToken) {
5175
4585
  return jsonError("Missing Vercel Sandbox OIDC token", 401);
@@ -5273,7 +4683,7 @@ async function proxySandboxEgressRequestImpl(request, deps) {
5273
4683
  request,
5274
4684
  upstreamUrl.hostname,
5275
4685
  deps.tracePropagation ?? {},
5276
- async () => await proxySandboxEgressVerifiedRequest({
4686
+ async () => await executeCredentialedEgressRequest({
5277
4687
  activeEgressId,
5278
4688
  credentialContext,
5279
4689
  deps,
@@ -5283,314 +4693,6 @@ async function proxySandboxEgressRequestImpl(request, deps) {
5283
4693
  })
5284
4694
  );
5285
4695
  }
5286
- async function proxySandboxEgressVerifiedRequest(input) {
5287
- const {
5288
- activeEgressId,
5289
- credentialContext,
5290
- deps,
5291
- provider,
5292
- request,
5293
- upstreamUrl
5294
- } = input;
5295
- let body;
5296
- let bodyRead = false;
5297
- if (isGrantSelectionBodyVisible({ provider, upstreamUrl })) {
5298
- body = await requestBodyBytes(request);
5299
- bodyRead = true;
5300
- }
5301
- const grantSelection = await selectSandboxEgressGrant({
5302
- bodyText: requestBodyText(body),
5303
- provider,
5304
- method: request.method,
5305
- upstreamUrl
5306
- });
5307
- let lease;
5308
- try {
5309
- lease = await sandboxEgressCredentialLease(
5310
- provider,
5311
- grantSelection,
5312
- credentialContext
5313
- );
5314
- } catch (error) {
5315
- if (error instanceof SandboxEgressCredentialError) {
5316
- await setSandboxEgressAuthRequiredSignal(credentialContext, {
5317
- provider: error.provider,
5318
- grant: error.grant,
5319
- kind: error.kind,
5320
- ...error.authorization ? { authorization: error.authorization } : {},
5321
- message: error.message
5322
- });
5323
- const isAuthRequired = error.kind === "auth_required";
5324
- logWarn(
5325
- isAuthRequired ? "sandbox_egress_credential_needed" : "sandbox_egress_credential_unavailable",
5326
- {},
5327
- {
5328
- ...egressAttributes({
5329
- egressId: activeEgressId,
5330
- grantAccess: error.grant.access,
5331
- grantName: error.grant.name,
5332
- grantReason: error.grant.reason,
5333
- host: upstreamUrl.hostname,
5334
- method: request.method,
5335
- path: upstreamUrl.pathname,
5336
- provider: error.provider,
5337
- status: 401
5338
- }),
5339
- ...routingAttributes(request, upstreamUrl)
5340
- },
5341
- isAuthRequired ? "Sandbox egress grant needs user authorization before issuing a credential lease" : "Sandbox egress credential lease is unavailable for selected grant"
5342
- );
5343
- return authRequiredResponse({
5344
- provider: error.provider,
5345
- grant: error.grant,
5346
- message: error.message
5347
- });
5348
- }
5349
- throw error;
5350
- }
5351
- if (!hasSandboxEgressLeaseTransformForHost(lease, upstreamUrl.hostname)) {
5352
- logWarn(
5353
- "sandbox_egress_transform_missing",
5354
- {},
5355
- {
5356
- ...egressAttributes({
5357
- egressId: activeEgressId,
5358
- grantAccess: lease.grant.access,
5359
- grantName: lease.grant.name,
5360
- grantReason: lease.grant.reason,
5361
- host: upstreamUrl.hostname,
5362
- method: request.method,
5363
- path: upstreamUrl.pathname,
5364
- provider,
5365
- status: 403
5366
- }),
5367
- "app.sandbox.egress.transform_domains": lease.headerTransforms.map(
5368
- (transform) => transform.domain
5369
- ),
5370
- ...routingAttributes(request, upstreamUrl)
5371
- },
5372
- "Sandbox egress credential lease does not cover forwarded host"
5373
- );
5374
- return jsonError("Credential lease does not cover forwarded host", 403);
5375
- }
5376
- const fetchImpl = deps.fetch ?? fetch;
5377
- const headers = requestHeaders(
5378
- request,
5379
- lease,
5380
- upstreamUrl.hostname,
5381
- deps.tracePropagation ?? {}
5382
- );
5383
- if (!bodyRead) {
5384
- body = await requestBodyBytes(request);
5385
- }
5386
- const intercepted = await deps.interceptHttp?.({
5387
- provider,
5388
- request: new Request(upstreamUrl, {
5389
- method: request.method,
5390
- headers,
5391
- ...body !== void 0 ? { body } : {}
5392
- }),
5393
- upstreamUrl
5394
- });
5395
- if (intercepted) {
5396
- return intercepted;
5397
- }
5398
- const upstream = await fetchImpl(upstreamUrl, {
5399
- method: request.method,
5400
- headers,
5401
- ...body !== void 0 ? { body } : {},
5402
- redirect: "manual"
5403
- });
5404
- try {
5405
- const effects = await onPluginEgressResponse({
5406
- provider,
5407
- grant: lease.grant,
5408
- method: request.method,
5409
- upstreamUrl,
5410
- response: {
5411
- headers: new Headers(upstream.headers),
5412
- readText: async (maxBytes) => await responseTextWithinLimit(upstream, maxBytes),
5413
- status: upstream.status
5414
- }
5415
- });
5416
- if (effects.permissionDenied) {
5417
- await setSandboxEgressPermissionDeniedSignal(credentialContext, {
5418
- provider,
5419
- grant: lease.grant,
5420
- ...lease.account ? { account: lease.account } : {},
5421
- message: effects.permissionDenied.message,
5422
- source: "upstream",
5423
- status: upstream.status,
5424
- upstreamHost: upstreamUrl.hostname,
5425
- upstreamPath: displayedUpstreamPath(upstreamUrl),
5426
- ...provider === "github" ? githubPermissionHeaders(upstream) : {}
5427
- });
5428
- logWarn(
5429
- "sandbox_egress_upstream_permission_classified",
5430
- {},
5431
- {
5432
- ...egressAttributes({
5433
- egressId: activeEgressId,
5434
- grantAccess: lease.grant.access,
5435
- grantName: lease.grant.name,
5436
- grantReason: lease.grant.reason,
5437
- host: upstreamUrl.hostname,
5438
- method: request.method,
5439
- path: upstreamUrl.pathname,
5440
- provider,
5441
- status: upstream.status
5442
- }),
5443
- ...routingAttributes(request, upstreamUrl),
5444
- ...upstreamPermissionAttributes(provider, upstream)
5445
- },
5446
- "Sandbox egress plugin classified upstream response as permission denied"
5447
- );
5448
- }
5449
- } catch (error) {
5450
- if (!isEgressAuthRequired(error)) {
5451
- throw error;
5452
- }
5453
- await clearSandboxEgressCredentialLease(
5454
- provider,
5455
- lease.grant.name,
5456
- credentialContext
5457
- );
5458
- await setSandboxEgressAuthRequiredSignal(credentialContext, {
5459
- provider,
5460
- grant: lease.grant,
5461
- kind: "auth_required",
5462
- ...error.authorization ?? lease.authorization ? { authorization: error.authorization ?? lease.authorization } : {},
5463
- message: error.message
5464
- });
5465
- logWarn(
5466
- "sandbox_egress_upstream_auth_required_classified",
5467
- {},
5468
- {
5469
- ...egressAttributes({
5470
- egressId: activeEgressId,
5471
- grantAccess: lease.grant.access,
5472
- grantName: lease.grant.name,
5473
- grantReason: lease.grant.reason,
5474
- host: upstreamUrl.hostname,
5475
- method: request.method,
5476
- path: upstreamUrl.pathname,
5477
- provider,
5478
- status: upstream.status
5479
- }),
5480
- ...routingAttributes(request, upstreamUrl),
5481
- ...upstreamPermissionAttributes(provider, upstream)
5482
- },
5483
- "Sandbox egress plugin classified upstream response as auth required"
5484
- );
5485
- await upstream.body?.cancel().catch(() => void 0);
5486
- return authRequiredResponse({
5487
- provider,
5488
- grant: lease.grant,
5489
- message: error.message
5490
- });
5491
- }
5492
- logSandboxEgressUpstreamRequest({
5493
- egressId: activeEgressId,
5494
- grantAccess: lease.grant.access,
5495
- grantName: lease.grant.name,
5496
- grantReason: lease.grant.reason,
5497
- provider,
5498
- request,
5499
- upstream,
5500
- upstreamUrl
5501
- });
5502
- if (upstream.status >= 400) {
5503
- logWarn(
5504
- "sandbox_egress_upstream_error_response",
5505
- {},
5506
- {
5507
- ...egressAttributes({
5508
- egressId: activeEgressId,
5509
- grantAccess: lease.grant.access,
5510
- grantName: lease.grant.name,
5511
- grantReason: lease.grant.reason,
5512
- host: upstreamUrl.hostname,
5513
- method: request.method,
5514
- path: upstreamUrl.pathname,
5515
- provider,
5516
- status: upstream.status
5517
- }),
5518
- ...routingAttributes(request, upstreamUrl),
5519
- ...upstreamPermissionAttributes(provider, upstream),
5520
- "error.type": `http_${upstream.status}`
5521
- },
5522
- `Sandbox egress upstream returned HTTP ${upstream.status}`
5523
- );
5524
- }
5525
- if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS || upstream.status === UPSTREAM_PERMISSION_REJECTION_STATUS) {
5526
- logWarn(
5527
- "sandbox_egress_upstream_auth_rejected",
5528
- {},
5529
- {
5530
- ...egressAttributes({
5531
- egressId: activeEgressId,
5532
- grantAccess: lease.grant.access,
5533
- grantName: lease.grant.name,
5534
- grantReason: lease.grant.reason,
5535
- host: upstreamUrl.hostname,
5536
- method: request.method,
5537
- path: upstreamUrl.pathname,
5538
- provider,
5539
- status: upstream.status
5540
- }),
5541
- ...routingAttributes(request, upstreamUrl),
5542
- ...upstreamPermissionAttributes(provider, upstream),
5543
- ...upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? {
5544
- "app.sandbox.egress.www_authenticate": upstream.headers.get("www-authenticate") ?? void 0
5545
- } : {}
5546
- },
5547
- upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? "Sandbox egress upstream auth rejected injected credential" : "Sandbox egress upstream permission denied"
5548
- );
5549
- if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS) {
5550
- await clearSandboxEgressCredentialLease(
5551
- provider,
5552
- lease.grant.name,
5553
- credentialContext
5554
- );
5555
- await setSandboxEgressAuthRequiredSignal(credentialContext, {
5556
- provider,
5557
- grant: lease.grant,
5558
- kind: "auth_required",
5559
- ...lease.authorization ? { authorization: lease.authorization } : {},
5560
- message: `Provider rejected the injected ${provider} credential.`
5561
- });
5562
- await upstream.body?.cancel().catch(() => void 0);
5563
- return authRequiredResponse({
5564
- provider,
5565
- grant: lease.grant,
5566
- message: `Provider rejected the injected ${provider} credential.
5567
- `
5568
- });
5569
- } else {
5570
- await clearSandboxEgressCredentialLease(
5571
- provider,
5572
- lease.grant.name,
5573
- credentialContext
5574
- );
5575
- await setSandboxEgressPermissionDeniedSignal(credentialContext, {
5576
- provider,
5577
- grant: lease.grant,
5578
- ...lease.account ? { account: lease.account } : {},
5579
- message: permissionDeniedMessage(provider, lease.grant),
5580
- source: "upstream",
5581
- status: UPSTREAM_PERMISSION_REJECTION_STATUS,
5582
- upstreamHost: upstreamUrl.hostname,
5583
- upstreamPath: displayedUpstreamPath(upstreamUrl),
5584
- ...provider === "github" ? githubPermissionHeaders(upstream) : {}
5585
- });
5586
- }
5587
- }
5588
- return new Response(upstream.body, {
5589
- status: upstream.status,
5590
- statusText: upstream.statusText,
5591
- headers: responseHeaders(upstream)
5592
- });
5593
- }
5594
4696
 
5595
4697
  // src/handlers/sandbox-egress-proxy.ts
5596
4698
  async function ALL(request, options = {}) {