Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions apps/cloud/src/observability/observability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,104 @@ 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,
code: secret,
"executor.ui.surface": secret,
"executor.ui.action": 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("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({
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,
});
});
});
30 changes: 30 additions & 0 deletions apps/cloud/src/observability/redact-span-urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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");
});
});
185 changes: 185 additions & 0 deletions e2e/cloud/auth-evidence.test.ts
Original file line number Diff line number Diff line change
@@ -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,
),
),
);
}),
);
Loading
Loading