From 15cae67430ceb1d3d0abe52e7803ad492f51350f Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:16:00 -0700 Subject: [PATCH 1/2] Verify diagnostic privacy and hosted content boundaries --- .../src/observability/observability.test.ts | 83 ++++++++ .../observability/redact-span-urls.test.ts | 30 +++ e2e/cloud/auth-evidence.test.ts | 185 ++++++++++++++++++ e2e/scenarios/artifact-preview-xss.test.ts | 76 +++++++ .../src/sdk/introspect-large-response.test.ts | 37 ++++ 5 files changed, 411 insertions(+) create mode 100644 e2e/cloud/auth-evidence.test.ts create mode 100644 e2e/scenarios/artifact-preview-xss.test.ts create mode 100644 packages/plugins/graphql/src/sdk/introspect-large-response.test.ts diff --git a/apps/cloud/src/observability/observability.test.ts b/apps/cloud/src/observability/observability.test.ts index 581377d12f..bf8d331af6 100644 --- a/apps/cloud/src/observability/observability.test.ts +++ b/apps/cloud/src/observability/observability.test.ts @@ -409,3 +409,86 @@ describe("Durable Object platform reset noise", () => { expect(options.beforeSend(event)).toBeNull(); }); }); + +describe("Sentry privacy boundary", () => { + it("rejects arbitrary values in classification tags and caller fingerprints", () => { + const secret = "SYNTHETIC_PRIVATE_MARKER"; + const sent = beforeSendCloudEvent({ + type: undefined, + fingerprint: [secret], + tags: { + operation: secret, + reason: secret, + status: secret, + otel_trace_id: secret, + otel_span_id: secret, + "mcp.do.cause_owner": secret, + }, + exception: { values: [{ type: secret, value: secret }] }, + }); + expect(sent).not.toBeNull(); + expect(JSON.stringify(sent)).not.toContain(secret); + expect(sent?.exception?.values?.[0]?.type).toBe("Error"); + }); + + it("retains known failure classifications and disables Sentry log payloads", () => { + const options = cloudSentryOptions({ SENTRY_DSN: "https://public@example.invalid/1" } as Env); + const sent = options.beforeSend({ + type: undefined, + tags: { operation: "getOrganization", reason: "connect_timeout", status: 503 }, + }); + expect(sent?.tags).toEqual({ + operation: "getOrganization", + reason: "connect_timeout", + status: 503, + }); + expect(options.enableLogs).toBe(false); + expect(options.sendDefaultPii).toBe(false); + }); + + it("strips secrets from auto-captured errors while retaining diagnostic locations", () => { + const secret = "SYNTHETIC_PRIVATE_MARKER"; + const sent = cloudSentryOptions({ + SENTRY_DSN: "https://public@example.invalid/1", + } as Env).beforeSend({ + type: undefined, + event_id: "safe-event-id", + message: secret, + user: { email: secret }, + request: { + url: `https://example.test/?token=${secret}`, + headers: { authorization: secret }, + data: secret, + }, + extra: { cause: secret }, + breadcrumbs: [{ message: secret }], + tags: { token: secret, otel_trace_id: traceId }, + exception: { + values: [ + { + type: "TypeError", + value: secret, + stacktrace: { + frames: [ + { + filename: `/assets/example.js?token=${secret}`, + function: "handleRequest", + lineno: 42, + vars: { secret }, + }, + ], + }, + }, + ], + }, + }); + expect(JSON.stringify(sent)).not.toContain(secret); + expect(sent?.event_id).toBe("safe-event-id"); + expect(sent?.tags?.otel_trace_id).toBe(traceId); + expect(sent?.exception?.values?.[0]?.stacktrace?.frames?.[0]).toMatchObject({ + filename: "/assets/example.js", + function: "handleRequest", + lineno: 42, + }); + }); +}); diff --git a/apps/cloud/src/observability/redact-span-urls.test.ts b/apps/cloud/src/observability/redact-span-urls.test.ts index 064f7a1e8b..942c1b2a41 100644 --- a/apps/cloud/src/observability/redact-span-urls.test.ts +++ b/apps/cloud/src/observability/redact-span-urls.test.ts @@ -47,6 +47,17 @@ const exportSpanWith = ( }; describe("UrlRedactingSpanProcessor", () => { + it("omits non-URL exception payloads recorded as span attributes", () => { + const secret = "SYNTHETIC_PRIVATE_MARKER"; + const exported = exportSpanWith({ + "exception.message": secret, + "exception.stacktrace": secret, + "http.request.method": "POST", + }); + expect(exported?.attributes["http.request.method"]).toBe("POST"); + expect(JSON.stringify(exported?.attributes)).not.toContain(secret); + }); + it("scrubs the span before the exporter sees it", () => { const exported = exportSpanWith({ "url.full": callbackUrl, @@ -284,3 +295,22 @@ describe("credential canary — no export channel carries the secret", () => { }, ); }); + +describe("non-URL secrets in exceptions", () => { + it("does not export a provider response or SQL values as error text", () => { + const secret = "synthetic-plain-secret"; + const exported = exportSpanWith({ "http.response.status_code": "500" }, (span) => { + span.recordException({ + name: "ProviderError", + message: `Failed query values: ${secret}`, + stack: `at provider: ${secret}`, + }); + span.setStatus({ code: SpanStatusCode.ERROR, message: secret }); + }); + expect(JSON.stringify({ events: exported?.events, status: exported?.status })).not.toContain( + secret, + ); + expect(exported?.status.code).toBe(SpanStatusCode.ERROR); + expect(exported?.events[0]?.attributes?.["exception.type"]).toBe("ProviderError"); + }); +}); diff --git a/e2e/cloud/auth-evidence.test.ts b/e2e/cloud/auth-evidence.test.ts new file mode 100644 index 0000000000..58d8876afd --- /dev/null +++ b/e2e/cloud/auth-evidence.test.ts @@ -0,0 +1,185 @@ +import { randomBytes } from "node:crypto"; +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { expect } from "@effect/vitest"; +import { Effect, Schema } from "effect"; + +import { RUNS_DIR, scenario } from "../src/scenario"; +import { RunDir, Target, Telemetry } from "../src/services"; + +const decodeString = Schema.decodeUnknownSync(Schema.String); + +const decodeKey = Schema.decodeUnknownSync( + Schema.Struct({ id: Schema.String, value: Schema.String }), +); + +scenario( + "Authentication · valid credentials work in headers but not query parameters", + {}, + Effect.gen(function* () { + const target = yield* Target; + const runDir = yield* RunDir; + const identity = yield* target.newIdentity(); + const keyResponse = yield* Effect.promise(() => + fetch(new URL("/api/account/api-keys", target.baseUrl), { + method: "POST", + headers: { + ...identity.headers, + origin: target.baseUrl, + "content-type": "application/json", + }, + body: JSON.stringify({ name: "authentication-evidence" }), + }), + ); + expect(keyResponse.status).toBe(200); + const key = decodeKey(yield* Effect.promise(() => keyResponse.json())); + yield* Effect.promise(async () => { + const cases: Array<{ surface: string; carrier: string; status: number }> = []; + for (const surface of ["api", "mcp"]) { + const send = async (query: string | null, header: boolean) => { + const url = new URL(surface === "api" ? "/api/policies" : "/mcp", target.baseUrl); + if (query) url.searchParams.set(query, key.value); + const response = await fetch(url, { + method: surface === "api" ? "GET" : "POST", + headers: { + accept: "application/json, text/event-stream", + "content-type": "application/json", + ...(header ? { authorization: `Bearer ${key.value}` } : {}), + }, + ...(surface === "mcp" + ? { + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { + protocolVersion: "2025-03-26", + capabilities: {}, + clientInfo: { name: "authentication-evidence", version: "1" }, + }, + }), + } + : {}), + }); + await response.text(); + cases.push({ + surface, + carrier: query ?? "Authorization header", + status: response.status, + }); + return response.status; + }; + expect(await send(null, true), `${surface} accepts the valid header credential`).toBe(200); + for (const query of [ + "api_key", + "apikey", + "key", + "token", + "access_token", + "authorization", + ]) { + expect(await send(query, false), `${surface} rejects query-only ${query}`).toBe( + surface === "api" ? 403 : 401, + ); + } + } + await writeFile( + join(runDir, "authentication-carriers.json"), + JSON.stringify({ cases }, null, 2), + ); + }).pipe( + Effect.ensuring( + Effect.promise(async () => { + const response = await fetch(new URL(`/api/account/api-keys/${key.id}`, target.baseUrl), { + method: "DELETE", + headers: { ...identity.headers, origin: target.baseUrl }, + }); + expect(response.status, "the disposable key is revoked").toBe(200); + }), + ), + ); + }), +); + +scenario( + "Authentication · successful login exports diagnostics without its credentials", + {}, + Effect.gen(function* () { + const target = yield* Target; + const telemetry = yield* Telemetry; + const runDir = yield* RunDir; + const identity = yield* target.newIdentity(); + const bootLog = join(RUNS_DIR, "cloud", "server-logs", "boot.log"); + const initialLogLength = (yield* Effect.promise(() => readFile(bootLog, "utf8"))).length; + const traceId = randomBytes(16).toString("hex"); + const headers = { traceparent: `00-${traceId}-${randomBytes(8).toString("hex")}-01` }; + const credentials = yield* Effect.promise(async () => { + const login = await fetch(new URL("/api/auth/login", target.baseUrl), { redirect: "manual" }); + expect(login.status).toBe(302); + expect(login.headers.get("referrer-policy")).toBe("no-referrer"); + const authorize = new URL(decodeString(login.headers.get("location"))); + const state = decodeString(authorize.searchParams.get("state")); + const stateCookie = login.headers + .getSetCookie() + .find((cookie) => cookie.startsWith("wos-login-state=")); + expect(stateCookie !== undefined).toBe(true); + authorize.searchParams.set("login_hint", identity.label); + const consent = await fetch(authorize, { redirect: "manual" }); + expect(consent.status).toBe(302); + const callback = new URL(decodeString(consent.headers.get("location"))); + const code = decodeString(callback.searchParams.get("code")); + const signedIn = await fetch(callback, { + redirect: "manual", + headers: { + ...headers, + cookie: decodeString(stateCookie).split(";")[0] ?? "", + }, + }); + expect(signedIn.status).toBe(302); + expect(signedIn.headers.get("referrer-policy")).toBe("no-referrer"); + const session = signedIn.headers + .getSetCookie() + .find((cookie) => cookie.startsWith("wos-session=")); + const sessionPair = decodeString(session).split(";")[0] ?? ""; + const verified = await fetch(new URL("/api/auth/me", target.baseUrl), { + headers: { cookie: sessionPair }, + }); + expect(verified.status).toBe(200); + return [state, code, sessionPair.slice("wos-session=".length)]; + }); + yield* telemetry.expectSpan({ traceId }); + const spans = yield* telemetry.searchSpans({ traceId }); + const exported = JSON.stringify(spans); + const logs = (yield* Effect.promise(() => readFile(bootLog, "utf8"))).slice(initialLogLength); + const matches = credentials.map((credential) => ({ + trace: exported.includes(credential), + serverLog: logs.includes(credential), + })); + // Assert booleans so even a failure cannot print a credential. + expect(matches.every((match) => !match.trace && !match.serverLog)).toBe(true); + yield* Effect.promise(() => + writeFile( + join(runDir, "login-diagnostics.json"), + JSON.stringify( + { + environment: "isolated cloud Worker with WorkOS emulator", + loginStatus: 302, + authenticatedSessionStatus: 200, + traceId, + exportedSpanCount: spans.length, + checkedCredentials: ["OAuth state", "authorization code", "sealed session cookie"], + credentialMatches: matches, + sample: spans.map(({ span }) => ({ + operation: span.operationName, + status: span.status, + attributeNames: Object.keys(span.tags), + })), + }, + null, + 2, + ), + ), + ); + }), +); diff --git a/e2e/scenarios/artifact-preview-xss.test.ts b/e2e/scenarios/artifact-preview-xss.test.ts new file mode 100644 index 0000000000..66eb1fa5f5 --- /dev/null +++ b/e2e/scenarios/artifact-preview-xss.test.ts @@ -0,0 +1,76 @@ +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; +import { AccountHttpApi } from "@executor-js/api"; +import { composePluginApi } from "@executor-js/api/server"; + +import { scenario } from "../src/scenario"; +import { Api, Browser, Target } from "../src/services"; +import { visit } from "../src/surfaces/browser"; + +const coreApi = composePluginApi([] as const); + +scenario( + "Artifacts · uploaded previews stay inert after storage and reload", + { timeout: 120_000 }, + Effect.gen(function* () { + const target = yield* Target; + const api = yield* Api; + const browser = yield* Browser; + const identity = yield* target.newIdentity(); + const client = yield* api.client(coreApi, identity); + const account = yield* api.client(AccountHttpApi, identity); + const me = yield* account.account.me(); + const title = "Preview security check"; + const marker = "Safe preview content"; + const artifact = yield* client.artifacts.save({ + payload: { + title, + code: "function App() { return
Preview security check
; }", + }, + }); + + yield* Effect.gen(function* () { + const uploaded = yield* client.artifacts.setPreview({ + params: { artifactId: artifact.id }, + payload: { + preview: + `
${marker}` + + "" + + '' + + "" + + "Unsafe link
", + }, + }); + expect(uploaded.stored).toBe(true); + const saved = yield* client.artifacts.get({ params: { artifactId: artifact.id } }); + expect(saved.preview?.markup).toBe(`
${marker}Unsafe link
`); + + yield* browser.session(identity, async ({ page, step }) => { + const galleryPath = me.organization?.slug + ? `/${me.organization.slug}/artifacts` + : "/artifacts"; + const card = page.locator('[data-slot="artifact-card"]').filter({ hasText: title }); + const preview = card.locator('[data-slot="artifact-preview"]'); + const checkPreview = async () => { + await preview.getByText(`${marker}Unsafe link`, { exact: true }).waitFor(); + expect( + await preview.locator("script, img, iframe, a, [onclick], [onerror]").count(), + ).toBe(0); + expect(await page.evaluate(() => document.body.dataset.previewXss)).toBeUndefined(); + }; + await step("Open the saved preview with injected markup removed", async () => { + await visit(page, `${target.baseUrl}${galleryPath}`); + await checkPreview(); + }); + await step("Reload and verify the stored preview remains inert", async () => { + await page.reload(); + await checkPreview(); + }); + }); + }).pipe( + Effect.ensuring( + client.artifacts.remove({ params: { artifactId: artifact.id } }).pipe(Effect.ignore), + ), + ); + }), +); diff --git a/packages/plugins/graphql/src/sdk/introspect-large-response.test.ts b/packages/plugins/graphql/src/sdk/introspect-large-response.test.ts new file mode 100644 index 0000000000..e42610d514 --- /dev/null +++ b/packages/plugins/graphql/src/sdk/introspect-large-response.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Layer } from "effect"; +import { HttpClient, HttpClientResponse } from "effect/unstable/http"; + +import { introspect } from "./introspect"; + +describe("GraphQL large introspection compatibility", () => { + it.effect("accepts a valid response larger than 32 MiB", () => + Effect.gen(function* () { + const description = "x".repeat(33 * 1024 * 1024); + const schema = { + queryType: { name: "Query" }, + mutationType: null, + types: [ + { + kind: "OBJECT", + name: "Query", + description, + fields: [], + inputFields: null, + enumValues: null, + }, + ], + }; + const client = HttpClient.make((request) => + Effect.succeed( + HttpClientResponse.fromWeb(request, Response.json({ data: { __schema: schema } })), + ), + ); + const result = yield* introspect("https://example.test/graphql").pipe( + Effect.provide(Layer.succeed(HttpClient.HttpClient)(client)), + ); + expect(result.__schema.queryType).toEqual({ name: "Query" }); + expect(result.__schema.types[0]?.description?.length).toBe(description.length); + }), + ); +}); From 66d00faa9b289c547c60f2eb119978af7de0d94c Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:27:09 -0700 Subject: [PATCH 2/2] Verify safe diagnostic reports through real requests --- .../src/observability/observability.test.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/observability/observability.test.ts b/apps/cloud/src/observability/observability.test.ts index bf8d331af6..878bdb8a80 100644 --- a/apps/cloud/src/observability/observability.test.ts +++ b/apps/cloud/src/observability/observability.test.ts @@ -423,6 +423,9 @@ describe("Sentry privacy boundary", () => { otel_trace_id: secret, otel_span_id: secret, "mcp.do.cause_owner": secret, + code: secret, + "executor.ui.surface": secret, + "executor.ui.action": secret, }, exception: { values: [{ type: secret, value: secret }] }, }); @@ -440,12 +443,27 @@ describe("Sentry privacy boundary", () => { expect(sent?.tags).toEqual({ operation: "getOrganization", reason: "connect_timeout", - status: 503, + status: "503", }); expect(options.enableLogs).toBe(false); expect(options.sendDefaultPii).toBe(false); }); + it("retains storage classifications without SQL or raw causes", () => { + const sent = beforeSendCloudEvent({ + type: undefined, + tags: { operation: "connection.create", code: "22021" }, + exception: { values: [{ type: "StorageError", value: "private SQL and bound values" }] }, + extra: { cause: "private SQL and bound values" }, + }); + expect(sent?.tags).toEqual({ operation: "connection.create", code: "22021" }); + expect(sent?.exception?.values?.[0]).toMatchObject({ + type: "StorageError", + value: "connection.create failed (22021)", + }); + expect(JSON.stringify(sent)).not.toContain("private SQL"); + }); + it("strips secrets from auto-captured errors while retaining diagnostic locations", () => { const secret = "SYNTHETIC_PRIVATE_MARKER"; const sent = cloudSentryOptions({