@sentry/junior 0.51.0 → 0.52.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.
- package/dist/app.js +218 -166
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -9138,84 +9138,51 @@ function createTracedStreamFn(base = streamSimple) {
|
|
|
9138
9138
|
// src/chat/sandbox/sandbox.ts
|
|
9139
9139
|
import fs4 from "fs/promises";
|
|
9140
9140
|
|
|
9141
|
-
// src/chat/sandbox/egress-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
]);
|
|
9150
|
-
return [...domains].sort((left, right) => left.localeCompare(right));
|
|
9151
|
-
}
|
|
9152
|
-
function providerEntries() {
|
|
9153
|
-
return getPluginProviders().map((plugin) => ({
|
|
9154
|
-
provider: plugin.manifest.name,
|
|
9155
|
-
domains: manifestDomains(plugin.manifest)
|
|
9156
|
-
})).filter((entry) => entry.domains.length > 0).sort((left, right) => left.provider.localeCompare(right.provider));
|
|
9157
|
-
}
|
|
9158
|
-
function resolveSandboxEgressProviderForHost(host) {
|
|
9159
|
-
return providerEntries().find(
|
|
9160
|
-
(entry) => entry.domains.some((domain) => matchesSandboxEgressDomain(host, domain))
|
|
9161
|
-
)?.provider;
|
|
9162
|
-
}
|
|
9163
|
-
function sandboxProxyUrl() {
|
|
9164
|
-
const baseUrl = resolveBaseUrl();
|
|
9165
|
-
if (!baseUrl) {
|
|
9166
|
-
throw new Error(
|
|
9167
|
-
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
9168
|
-
);
|
|
9169
|
-
}
|
|
9170
|
-
return new URL("/", baseUrl).toString();
|
|
9141
|
+
// src/chat/sandbox/egress-session.ts
|
|
9142
|
+
import { createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
|
|
9143
|
+
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
9144
|
+
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
9145
|
+
var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
|
|
9146
|
+
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
9147
|
+
function leaseKey(provider, context) {
|
|
9148
|
+
return `${SANDBOX_EGRESS_LEASE_PREFIX}:${provider}:${context.requesterId}:${context.egressId}:${context.contextId}`;
|
|
9171
9149
|
}
|
|
9172
|
-
function
|
|
9173
|
-
const
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
const entries = providerEntries();
|
|
9177
|
-
if (entries.length === 0) {
|
|
9178
|
-
return { allow };
|
|
9150
|
+
function getSandboxEgressSecret() {
|
|
9151
|
+
const explicit = process.env.JUNIOR_SANDBOX_EGRESS_SECRET?.trim();
|
|
9152
|
+
if (explicit) {
|
|
9153
|
+
return explicit;
|
|
9179
9154
|
}
|
|
9180
|
-
const
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
allow[domain] = [{ forwardURL }];
|
|
9184
|
-
}
|
|
9155
|
+
const sharedInternal = process.env.JUNIOR_INTERNAL_RESUME_SECRET?.trim();
|
|
9156
|
+
if (sharedInternal) {
|
|
9157
|
+
return sharedInternal;
|
|
9185
9158
|
}
|
|
9186
|
-
|
|
9159
|
+
throw new Error(
|
|
9160
|
+
"Cannot determine sandbox egress secret (set JUNIOR_SANDBOX_EGRESS_SECRET or JUNIOR_INTERNAL_RESUME_SECRET)"
|
|
9161
|
+
);
|
|
9187
9162
|
}
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
for (const plugin of getPluginProviders().sort(
|
|
9191
|
-
(left, right) => left.manifest.name.localeCompare(right.manifest.name)
|
|
9192
|
-
)) {
|
|
9193
|
-
Object.assign(env, resolvePluginCommandEnv(plugin.manifest));
|
|
9194
|
-
const credentials = plugin.manifest.credentials;
|
|
9195
|
-
if (credentials) {
|
|
9196
|
-
env[credentials.authTokenEnv] = resolveAuthTokenPlaceholder(credentials);
|
|
9197
|
-
}
|
|
9198
|
-
}
|
|
9199
|
-
return env;
|
|
9163
|
+
function base64Url(input) {
|
|
9164
|
+
return Buffer.from(input, "utf8").toString("base64url");
|
|
9200
9165
|
}
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
9204
|
-
var SANDBOX_EGRESS_SESSION_PREFIX = "sandbox-egress-session";
|
|
9205
|
-
var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
|
|
9206
|
-
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
9207
|
-
function sessionKey2(egressId) {
|
|
9208
|
-
return `${SANDBOX_EGRESS_SESSION_PREFIX}:${egressId}`;
|
|
9166
|
+
function fromBase64Url(input) {
|
|
9167
|
+
return Buffer.from(input, "base64url").toString("utf8");
|
|
9209
9168
|
}
|
|
9210
|
-
function
|
|
9211
|
-
return
|
|
9169
|
+
function signPayload(payload) {
|
|
9170
|
+
return createHmac("sha256", getSandboxEgressSecret()).update(payload).digest("base64url");
|
|
9212
9171
|
}
|
|
9213
|
-
function
|
|
9172
|
+
function timingSafeMatch(expected, actual) {
|
|
9173
|
+
const expectedBuffer = Buffer.from(expected);
|
|
9174
|
+
const actualBuffer = Buffer.from(actual);
|
|
9175
|
+
if (expectedBuffer.length !== actualBuffer.length) {
|
|
9176
|
+
return false;
|
|
9177
|
+
}
|
|
9178
|
+
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
9179
|
+
}
|
|
9180
|
+
function parseRequesterContext(value) {
|
|
9214
9181
|
if (!value || typeof value !== "object") {
|
|
9215
9182
|
return void 0;
|
|
9216
9183
|
}
|
|
9217
9184
|
const record = value;
|
|
9218
|
-
if (typeof record.requesterId !== "string" || typeof record.expiresAtMs !== "number" || !Number.isFinite(record.expiresAtMs) || typeof record.
|
|
9185
|
+
if (typeof record.requesterId !== "string" || !record.requesterId || typeof record.egressId !== "string" || !record.egressId || typeof record.expiresAtMs !== "number" || !Number.isFinite(record.expiresAtMs) || typeof record.contextId !== "string" || !record.contextId) {
|
|
9219
9186
|
return void 0;
|
|
9220
9187
|
}
|
|
9221
9188
|
if (record.expiresAtMs <= Date.now()) {
|
|
@@ -9223,8 +9190,9 @@ function parseSession(value) {
|
|
|
9223
9190
|
}
|
|
9224
9191
|
return {
|
|
9225
9192
|
requesterId: record.requesterId,
|
|
9193
|
+
egressId: record.egressId,
|
|
9226
9194
|
expiresAtMs: record.expiresAtMs,
|
|
9227
|
-
|
|
9195
|
+
contextId: record.contextId
|
|
9228
9196
|
};
|
|
9229
9197
|
}
|
|
9230
9198
|
function parseLease(value) {
|
|
@@ -9253,50 +9221,127 @@ function parseLease(value) {
|
|
|
9253
9221
|
headerTransforms
|
|
9254
9222
|
};
|
|
9255
9223
|
}
|
|
9256
|
-
|
|
9257
|
-
const state = getStateAdapter();
|
|
9258
|
-
await state.connect();
|
|
9224
|
+
function createSandboxEgressRequesterToken(input) {
|
|
9259
9225
|
const ttlMs = Math.max(1, input.ttlMs ?? DEFAULT_SESSION_TTL_MS);
|
|
9260
9226
|
const now = Date.now();
|
|
9261
|
-
const
|
|
9227
|
+
const context = {
|
|
9262
9228
|
requesterId: input.requesterId,
|
|
9229
|
+
egressId: input.egressId,
|
|
9263
9230
|
expiresAtMs: now + ttlMs,
|
|
9264
|
-
|
|
9231
|
+
contextId: randomUUID3()
|
|
9265
9232
|
};
|
|
9266
|
-
|
|
9233
|
+
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
9234
|
+
JSON.stringify(context)
|
|
9235
|
+
)}`;
|
|
9236
|
+
return `${payload}.${signPayload(payload)}`;
|
|
9267
9237
|
}
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9238
|
+
function parseSandboxEgressRequesterToken(token) {
|
|
9239
|
+
if (!token) {
|
|
9240
|
+
return void 0;
|
|
9241
|
+
}
|
|
9242
|
+
const parts = token.split(".");
|
|
9243
|
+
if (parts.length !== 3 || parts[0] !== SANDBOX_EGRESS_TOKEN_VERSION) {
|
|
9244
|
+
return void 0;
|
|
9245
|
+
}
|
|
9246
|
+
const encodedSession = parts[1];
|
|
9247
|
+
const signature = parts[2];
|
|
9248
|
+
if (!encodedSession || !signature) {
|
|
9249
|
+
return void 0;
|
|
9250
|
+
}
|
|
9251
|
+
const payload = `${parts[0]}.${encodedSession}`;
|
|
9252
|
+
if (!timingSafeMatch(signPayload(payload), signature)) {
|
|
9253
|
+
return void 0;
|
|
9254
|
+
}
|
|
9255
|
+
try {
|
|
9256
|
+
return parseRequesterContext(JSON.parse(fromBase64Url(encodedSession)));
|
|
9257
|
+
} catch {
|
|
9258
|
+
return void 0;
|
|
9259
|
+
}
|
|
9277
9260
|
}
|
|
9278
|
-
async function setSandboxEgressCredentialLease(
|
|
9261
|
+
async function setSandboxEgressCredentialLease(context, lease) {
|
|
9279
9262
|
const leaseExpiresAtMs = Date.parse(lease.expiresAt);
|
|
9280
9263
|
if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
|
|
9281
9264
|
return;
|
|
9282
9265
|
}
|
|
9283
9266
|
const ttlMs = Math.max(
|
|
9284
9267
|
1,
|
|
9285
|
-
Math.min(leaseExpiresAtMs,
|
|
9268
|
+
Math.min(leaseExpiresAtMs, context.expiresAtMs) - Date.now()
|
|
9286
9269
|
);
|
|
9287
9270
|
const state = getStateAdapter();
|
|
9288
9271
|
await state.connect();
|
|
9289
|
-
await state.set(leaseKey(
|
|
9272
|
+
await state.set(leaseKey(lease.provider, context), lease, ttlMs);
|
|
9290
9273
|
}
|
|
9291
|
-
async function getSandboxEgressCredentialLease(
|
|
9274
|
+
async function getSandboxEgressCredentialLease(provider, context) {
|
|
9292
9275
|
const state = getStateAdapter();
|
|
9293
9276
|
await state.connect();
|
|
9294
|
-
return parseLease(await state.get(leaseKey(
|
|
9277
|
+
return parseLease(await state.get(leaseKey(provider, context)));
|
|
9295
9278
|
}
|
|
9296
|
-
async function clearSandboxEgressCredentialLease(
|
|
9279
|
+
async function clearSandboxEgressCredentialLease(provider, context) {
|
|
9297
9280
|
const state = getStateAdapter();
|
|
9298
9281
|
await state.connect();
|
|
9299
|
-
await state.delete(leaseKey(
|
|
9282
|
+
await state.delete(leaseKey(provider, context));
|
|
9283
|
+
}
|
|
9284
|
+
|
|
9285
|
+
// src/chat/sandbox/egress-policy.ts
|
|
9286
|
+
function matchesSandboxEgressDomain(host, domain) {
|
|
9287
|
+
return host.toLowerCase() === domain.toLowerCase();
|
|
9288
|
+
}
|
|
9289
|
+
function manifestDomains(manifest) {
|
|
9290
|
+
const domains = /* @__PURE__ */ new Set([
|
|
9291
|
+
...manifest.credentials?.domains ?? [],
|
|
9292
|
+
...manifest.domains ?? []
|
|
9293
|
+
]);
|
|
9294
|
+
return [...domains].sort((left, right) => left.localeCompare(right));
|
|
9295
|
+
}
|
|
9296
|
+
function providerEntries() {
|
|
9297
|
+
return getPluginProviders().map((plugin) => ({
|
|
9298
|
+
provider: plugin.manifest.name,
|
|
9299
|
+
domains: manifestDomains(plugin.manifest)
|
|
9300
|
+
})).filter((entry) => entry.domains.length > 0).sort((left, right) => left.provider.localeCompare(right.provider));
|
|
9301
|
+
}
|
|
9302
|
+
function resolveSandboxEgressProviderForHost(host) {
|
|
9303
|
+
return providerEntries().find(
|
|
9304
|
+
(entry) => entry.domains.some((domain) => matchesSandboxEgressDomain(host, domain))
|
|
9305
|
+
)?.provider;
|
|
9306
|
+
}
|
|
9307
|
+
function sandboxProxyUrl(requesterToken) {
|
|
9308
|
+
const baseUrl = resolveBaseUrl();
|
|
9309
|
+
if (!baseUrl) {
|
|
9310
|
+
throw new Error(
|
|
9311
|
+
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
9312
|
+
);
|
|
9313
|
+
}
|
|
9314
|
+
const path11 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
|
|
9315
|
+
return new URL(path11, baseUrl).toString();
|
|
9316
|
+
}
|
|
9317
|
+
function buildSandboxEgressNetworkPolicy(input) {
|
|
9318
|
+
const allow = {
|
|
9319
|
+
"*": []
|
|
9320
|
+
};
|
|
9321
|
+
const entries = providerEntries();
|
|
9322
|
+
if (entries.length === 0) {
|
|
9323
|
+
return { allow };
|
|
9324
|
+
}
|
|
9325
|
+
const forwardURL = sandboxProxyUrl(input?.requesterToken);
|
|
9326
|
+
for (const entry of entries) {
|
|
9327
|
+
for (const domain of entry.domains) {
|
|
9328
|
+
allow[domain] = [{ forwardURL }];
|
|
9329
|
+
}
|
|
9330
|
+
}
|
|
9331
|
+
return { allow };
|
|
9332
|
+
}
|
|
9333
|
+
async function resolveSandboxCommandEnvironment() {
|
|
9334
|
+
const env = {};
|
|
9335
|
+
for (const plugin of getPluginProviders().sort(
|
|
9336
|
+
(left, right) => left.manifest.name.localeCompare(right.manifest.name)
|
|
9337
|
+
)) {
|
|
9338
|
+
Object.assign(env, resolvePluginCommandEnv(plugin.manifest));
|
|
9339
|
+
const credentials = plugin.manifest.credentials;
|
|
9340
|
+
if (credentials) {
|
|
9341
|
+
env[credentials.authTokenEnv] = resolveAuthTokenPlaceholder(credentials);
|
|
9342
|
+
}
|
|
9343
|
+
}
|
|
9344
|
+
return env;
|
|
9300
9345
|
}
|
|
9301
9346
|
|
|
9302
9347
|
// src/chat/sandbox/http-error-details.ts
|
|
@@ -10601,7 +10646,6 @@ function createSandboxSessionManager(options) {
|
|
|
10601
10646
|
}
|
|
10602
10647
|
};
|
|
10603
10648
|
const buildToolExecutors = async (sandboxInstance) => {
|
|
10604
|
-
const activeSandboxId = sandboxInstance.sandboxId;
|
|
10605
10649
|
const toolkit = await withSandboxSpan(
|
|
10606
10650
|
"sandbox.bash_tool.init",
|
|
10607
10651
|
"sandbox.tool.init",
|
|
@@ -10621,35 +10665,9 @@ function createSandboxSessionManager(options) {
|
|
|
10621
10665
|
}
|
|
10622
10666
|
return {
|
|
10623
10667
|
bash: async (input) => {
|
|
10624
|
-
const commandEgressId = sandboxInstance.sandboxEgressId;
|
|
10625
10668
|
let timedOut = false;
|
|
10626
10669
|
let timeoutId;
|
|
10627
|
-
let commandFinished = false;
|
|
10628
|
-
const finishCommand = async () => {
|
|
10629
|
-
if (commandFinished) {
|
|
10630
|
-
return;
|
|
10631
|
-
}
|
|
10632
|
-
commandFinished = true;
|
|
10633
|
-
await options?.afterCommand?.(commandEgressId);
|
|
10634
|
-
await refreshNetworkPolicy(sandboxInstance);
|
|
10635
|
-
};
|
|
10636
|
-
const finishCommandBestEffort = async () => {
|
|
10637
|
-
try {
|
|
10638
|
-
await finishCommand();
|
|
10639
|
-
} catch (error) {
|
|
10640
|
-
logWarn(
|
|
10641
|
-
"sandbox_command_cleanup_failed",
|
|
10642
|
-
traceContext,
|
|
10643
|
-
{
|
|
10644
|
-
"app.sandbox.id": activeSandboxId,
|
|
10645
|
-
"error.type": error instanceof Error ? error.name : "sandbox_command_cleanup_error"
|
|
10646
|
-
},
|
|
10647
|
-
"Sandbox command cleanup failed"
|
|
10648
|
-
);
|
|
10649
|
-
}
|
|
10650
|
-
};
|
|
10651
10670
|
try {
|
|
10652
|
-
await options?.beforeCommand?.(commandEgressId);
|
|
10653
10671
|
await refreshNetworkPolicy(sandboxInstance);
|
|
10654
10672
|
const sandboxCommandEnv = await resolveCommandEnv();
|
|
10655
10673
|
const script = buildNonInteractiveShellScript(input.command, {
|
|
@@ -10671,7 +10689,6 @@ function createSandboxSessionManager(options) {
|
|
|
10671
10689
|
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
10672
10690
|
...controller ? { signal: controller.signal } : {}
|
|
10673
10691
|
});
|
|
10674
|
-
await finishCommandBestEffort();
|
|
10675
10692
|
return await readCommandOutput(commandResult2);
|
|
10676
10693
|
} catch (error) {
|
|
10677
10694
|
if (timedOut) {
|
|
@@ -10692,7 +10709,6 @@ function createSandboxSessionManager(options) {
|
|
|
10692
10709
|
if (timeoutId) {
|
|
10693
10710
|
clearTimeout(timeoutId);
|
|
10694
10711
|
}
|
|
10695
|
-
await finishCommandBestEffort();
|
|
10696
10712
|
}
|
|
10697
10713
|
},
|
|
10698
10714
|
readFile: async (input) => await executeReadFile(input, {
|
|
@@ -10781,25 +10797,40 @@ function createSandboxExecutor(options) {
|
|
|
10781
10797
|
let referenceFiles = [];
|
|
10782
10798
|
const traceContext = options?.traceContext ?? {};
|
|
10783
10799
|
const credentialEgress = options?.credentialEgress;
|
|
10784
|
-
const
|
|
10785
|
-
|
|
10786
|
-
|
|
10800
|
+
const sandboxEgressTokenTtlMs = Math.max(
|
|
10801
|
+
1,
|
|
10802
|
+
options?.timeoutMs ?? 1e3 * 60 * 30
|
|
10803
|
+
);
|
|
10804
|
+
const sandboxEgressRequesterTokens = /* @__PURE__ */ new Map();
|
|
10805
|
+
const sandboxEgressRequesterTokenFor = (egressId) => {
|
|
10806
|
+
const cached = sandboxEgressRequesterTokens.get(egressId);
|
|
10807
|
+
if (cached && cached.expiresAtMs > Date.now()) {
|
|
10808
|
+
return cached.token;
|
|
10809
|
+
}
|
|
10810
|
+
if (!credentialEgress) {
|
|
10811
|
+
throw new Error("Sandbox credential egress is not configured");
|
|
10812
|
+
}
|
|
10813
|
+
const now = Date.now();
|
|
10814
|
+
const token = createSandboxEgressRequesterToken({
|
|
10787
10815
|
requesterId: credentialEgress.requesterId,
|
|
10788
|
-
|
|
10816
|
+
egressId,
|
|
10817
|
+
ttlMs: sandboxEgressTokenTtlMs
|
|
10789
10818
|
});
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10819
|
+
sandboxEgressRequesterTokens.set(egressId, {
|
|
10820
|
+
expiresAtMs: now + sandboxEgressTokenTtlMs,
|
|
10821
|
+
token
|
|
10822
|
+
});
|
|
10823
|
+
return token;
|
|
10824
|
+
};
|
|
10794
10825
|
const sessionManager = createSandboxSessionManager({
|
|
10795
10826
|
sandboxId: options?.sandboxId,
|
|
10796
10827
|
sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
|
|
10797
10828
|
timeoutMs: options?.timeoutMs,
|
|
10798
10829
|
traceContext,
|
|
10799
10830
|
commandEnv: credentialEgress ? async () => await resolveSandboxCommandEnvironment() : void 0,
|
|
10800
|
-
createNetworkPolicy: credentialEgress ?
|
|
10801
|
-
|
|
10802
|
-
|
|
10831
|
+
createNetworkPolicy: credentialEgress ? (egressId) => buildSandboxEgressNetworkPolicy({
|
|
10832
|
+
requesterToken: sandboxEgressRequesterTokenFor(egressId)
|
|
10833
|
+
}) : void 0,
|
|
10803
10834
|
onSandboxAcquired: async (sandbox) => {
|
|
10804
10835
|
await options?.onSandboxAcquired?.(sandbox);
|
|
10805
10836
|
}
|
|
@@ -14927,7 +14958,7 @@ async function persistAuthPauseTurnState(args) {
|
|
|
14927
14958
|
}
|
|
14928
14959
|
|
|
14929
14960
|
// src/chat/services/timeout-resume.ts
|
|
14930
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
14961
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
14931
14962
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
14932
14963
|
var TURN_TIMEOUT_RESUME_SIGNATURE_VERSION = "v1";
|
|
14933
14964
|
var TURN_TIMEOUT_RESUME_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
@@ -14962,16 +14993,16 @@ function buildSignedPayload(timestamp, body) {
|
|
|
14962
14993
|
return `${timestamp}:${body}`;
|
|
14963
14994
|
}
|
|
14964
14995
|
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
14965
|
-
const digest =
|
|
14996
|
+
const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
|
|
14966
14997
|
return `${TURN_TIMEOUT_RESUME_SIGNATURE_VERSION}=${digest}`;
|
|
14967
14998
|
}
|
|
14968
|
-
function
|
|
14999
|
+
function timingSafeMatch2(expected, actual) {
|
|
14969
15000
|
const expectedBuffer = Buffer.from(expected);
|
|
14970
15001
|
const actualBuffer = Buffer.from(actual);
|
|
14971
15002
|
if (expectedBuffer.length !== actualBuffer.length) {
|
|
14972
15003
|
return false;
|
|
14973
15004
|
}
|
|
14974
|
-
return
|
|
15005
|
+
return timingSafeEqual2(expectedBuffer, actualBuffer);
|
|
14975
15006
|
}
|
|
14976
15007
|
function parseTurnTimeoutResumeRequest(value) {
|
|
14977
15008
|
if (!value || typeof value !== "object") {
|
|
@@ -15034,7 +15065,7 @@ async function verifyTurnTimeoutResumeRequest(request) {
|
|
|
15034
15065
|
}
|
|
15035
15066
|
const body = await request.text();
|
|
15036
15067
|
const expectedSignature = signTurnTimeoutResumeBody(secret, timestamp, body);
|
|
15037
|
-
if (!
|
|
15068
|
+
if (!timingSafeMatch2(expectedSignature, signature)) {
|
|
15038
15069
|
return void 0;
|
|
15039
15070
|
}
|
|
15040
15071
|
try {
|
|
@@ -16069,10 +16100,32 @@ function egressAttributes(input) {
|
|
|
16069
16100
|
...input.status ? { "http.response.status_code": input.status } : {}
|
|
16070
16101
|
};
|
|
16071
16102
|
}
|
|
16103
|
+
function requesterTokenFromRequest(request) {
|
|
16104
|
+
const pathname = new URL(request.url).pathname;
|
|
16105
|
+
const prefix = `${SANDBOX_EGRESS_PROXY_PATH}/`;
|
|
16106
|
+
if (!pathname.startsWith(prefix)) {
|
|
16107
|
+
return void 0;
|
|
16108
|
+
}
|
|
16109
|
+
const token = pathname.slice(prefix.length).split("/")[0];
|
|
16110
|
+
if (!token) {
|
|
16111
|
+
return void 0;
|
|
16112
|
+
}
|
|
16113
|
+
try {
|
|
16114
|
+
return decodeURIComponent(token);
|
|
16115
|
+
} catch {
|
|
16116
|
+
return void 0;
|
|
16117
|
+
}
|
|
16118
|
+
}
|
|
16119
|
+
function redactedProxyPath(pathname) {
|
|
16120
|
+
if (pathname.startsWith(`${SANDBOX_EGRESS_PROXY_PATH}/`)) {
|
|
16121
|
+
return `${SANDBOX_EGRESS_PROXY_PATH}/<token>`;
|
|
16122
|
+
}
|
|
16123
|
+
return pathname;
|
|
16124
|
+
}
|
|
16072
16125
|
function routingAttributes(request, upstreamUrl) {
|
|
16073
16126
|
const proxyUrl = new URL(request.url);
|
|
16074
16127
|
const attributes = {
|
|
16075
|
-
"app.sandbox.egress.proxy_path": proxyUrl.pathname
|
|
16128
|
+
"app.sandbox.egress.proxy_path": redactedProxyPath(proxyUrl.pathname)
|
|
16076
16129
|
};
|
|
16077
16130
|
if (upstreamUrl) {
|
|
16078
16131
|
attributes["app.sandbox.egress.upstream_path"] = upstreamUrl.pathname;
|
|
@@ -16125,22 +16178,23 @@ function normalizePort(value) {
|
|
|
16125
16178
|
function sandboxIdFromPayload(payload) {
|
|
16126
16179
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
16127
16180
|
}
|
|
16181
|
+
function normalizedForwardedPath(path11) {
|
|
16182
|
+
if (!path11.startsWith("/") || path11.startsWith("//") || path11.includes("#") || /[\r\n]/.test(path11)) {
|
|
16183
|
+
return { ok: false, error: "Invalid forwarded path" };
|
|
16184
|
+
}
|
|
16185
|
+
try {
|
|
16186
|
+
const url = new URL(path11, "https://sandbox-forwarded.local");
|
|
16187
|
+
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
16188
|
+
} catch {
|
|
16189
|
+
return { ok: false, error: "Invalid forwarded path" };
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16128
16192
|
function upstreamPath(request) {
|
|
16129
16193
|
const forwardedPath = request.headers.get(FORWARDED_PATH_HEADER);
|
|
16130
|
-
if (forwardedPath?.trim()) {
|
|
16131
|
-
|
|
16132
|
-
if (!path11.startsWith("/") || path11.startsWith("//") || path11.includes("#") || /[\r\n]/.test(path11)) {
|
|
16133
|
-
return { ok: false, error: "Invalid forwarded path" };
|
|
16134
|
-
}
|
|
16135
|
-
try {
|
|
16136
|
-
const url2 = new URL(path11, "https://sandbox-forwarded.local");
|
|
16137
|
-
return { ok: true, path: `${url2.pathname}${url2.search}` };
|
|
16138
|
-
} catch {
|
|
16139
|
-
return { ok: false, error: "Invalid forwarded path" };
|
|
16140
|
-
}
|
|
16194
|
+
if (!forwardedPath?.trim()) {
|
|
16195
|
+
return { ok: false, error: "Missing forwarded path" };
|
|
16141
16196
|
}
|
|
16142
|
-
|
|
16143
|
-
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
16197
|
+
return normalizedForwardedPath(forwardedPath.trim());
|
|
16144
16198
|
}
|
|
16145
16199
|
function buildUpstreamUrl(request) {
|
|
16146
16200
|
const forwardedHost = request.headers.get(FORWARDED_HOST_HEADER);
|
|
@@ -16212,18 +16266,14 @@ function responseHeaders(upstream) {
|
|
|
16212
16266
|
});
|
|
16213
16267
|
return headers;
|
|
16214
16268
|
}
|
|
16215
|
-
async function credentialLease(
|
|
16216
|
-
const cached = await getSandboxEgressCredentialLease(
|
|
16217
|
-
egressId,
|
|
16218
|
-
provider,
|
|
16219
|
-
session
|
|
16220
|
-
);
|
|
16269
|
+
async function credentialLease(provider, context) {
|
|
16270
|
+
const cached = await getSandboxEgressCredentialLease(provider, context);
|
|
16221
16271
|
if (cached) {
|
|
16222
16272
|
return cached;
|
|
16223
16273
|
}
|
|
16224
16274
|
const lease = await issueProviderCredentialLease({
|
|
16225
16275
|
provider,
|
|
16226
|
-
requesterId:
|
|
16276
|
+
requesterId: context.requesterId,
|
|
16227
16277
|
reason: `sandbox-egress:${provider}`
|
|
16228
16278
|
});
|
|
16229
16279
|
const headerTransforms = lease.headerTransforms ?? [];
|
|
@@ -16237,7 +16287,7 @@ async function credentialLease(egressId, provider, session) {
|
|
|
16237
16287
|
expiresAt: lease.expiresAt,
|
|
16238
16288
|
headerTransforms
|
|
16239
16289
|
};
|
|
16240
|
-
await setSandboxEgressCredentialLease(
|
|
16290
|
+
await setSandboxEgressCredentialLease(context, cachedLease);
|
|
16241
16291
|
return cachedLease;
|
|
16242
16292
|
}
|
|
16243
16293
|
function hasTransformForHost(lease, host) {
|
|
@@ -16278,7 +16328,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
16278
16328
|
{},
|
|
16279
16329
|
{
|
|
16280
16330
|
"http.request.method": request.method,
|
|
16281
|
-
"url.path": new URL(request.url).pathname
|
|
16331
|
+
"url.path": redactedProxyPath(new URL(request.url).pathname)
|
|
16282
16332
|
},
|
|
16283
16333
|
"Sandbox egress OIDC payload did not include a VM session id"
|
|
16284
16334
|
);
|
|
@@ -16296,7 +16346,7 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
16296
16346
|
...egressAttributes({
|
|
16297
16347
|
egressId: activeEgressId,
|
|
16298
16348
|
method: request.method,
|
|
16299
|
-
path: new URL(request.url).pathname,
|
|
16349
|
+
path: redactedProxyPath(new URL(request.url).pathname),
|
|
16300
16350
|
status: 400
|
|
16301
16351
|
}),
|
|
16302
16352
|
...routingAttributes(request)
|
|
@@ -16325,10 +16375,12 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
16325
16375
|
);
|
|
16326
16376
|
return jsonError("No provider owns forwarded host", 403);
|
|
16327
16377
|
}
|
|
16328
|
-
const
|
|
16329
|
-
|
|
16378
|
+
const requesterContext = parseSandboxEgressRequesterToken(
|
|
16379
|
+
requesterTokenFromRequest(request)
|
|
16380
|
+
);
|
|
16381
|
+
if (!requesterContext || requesterContext.egressId !== activeEgressId) {
|
|
16330
16382
|
logWarn(
|
|
16331
|
-
"
|
|
16383
|
+
"sandbox_egress_requester_context_unauthorized",
|
|
16332
16384
|
{},
|
|
16333
16385
|
{
|
|
16334
16386
|
...egressAttributes({
|
|
@@ -16341,13 +16393,13 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
16341
16393
|
}),
|
|
16342
16394
|
...routingAttributes(request, upstreamUrl)
|
|
16343
16395
|
},
|
|
16344
|
-
"Sandbox egress
|
|
16396
|
+
"Sandbox egress request did not include a valid requester context for the VM session"
|
|
16345
16397
|
);
|
|
16346
|
-
return jsonError("Sandbox egress
|
|
16398
|
+
return jsonError("Sandbox egress requester context is not authorized", 403);
|
|
16347
16399
|
}
|
|
16348
16400
|
let lease;
|
|
16349
16401
|
try {
|
|
16350
|
-
lease = await credentialLease(
|
|
16402
|
+
lease = await credentialLease(provider, requesterContext);
|
|
16351
16403
|
} catch (error) {
|
|
16352
16404
|
if (error instanceof CredentialUnavailableError) {
|
|
16353
16405
|
logWarn(
|
|
@@ -16451,7 +16503,7 @@ ${error.message}`,
|
|
|
16451
16503
|
},
|
|
16452
16504
|
"Sandbox egress upstream auth rejected"
|
|
16453
16505
|
);
|
|
16454
|
-
await clearSandboxEgressCredentialLease(
|
|
16506
|
+
await clearSandboxEgressCredentialLease(provider, requesterContext);
|
|
16455
16507
|
}
|
|
16456
16508
|
return new Response(upstream.body, {
|
|
16457
16509
|
status: upstream.status,
|