From fb8b86f6fad976a6c547ff711497e142181e2ed0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 05:27:22 -0700 Subject: [PATCH 1/4] [codex] Structure relay environment connector errors Co-authored-by: codex --- .../environments/EnvironmentConnector.test.ts | 68 ++++++- .../src/environments/EnvironmentConnector.ts | 190 ++++++++++++++---- 2 files changed, 213 insertions(+), 45 deletions(-) diff --git a/infra/relay/src/environments/EnvironmentConnector.test.ts b/infra/relay/src/environments/EnvironmentConnector.test.ts index 63f12379870..e4e318cc7e4 100644 --- a/infra/relay/src/environments/EnvironmentConnector.test.ts +++ b/infra/relay/src/environments/EnvironmentConnector.test.ts @@ -56,6 +56,10 @@ const decodeMintRequestBody = Schema.decodeUnknownSync( const isEnvironmentConnectNotAuthorized = Schema.is( EnvironmentConnector.EnvironmentConnectNotAuthorized, ); +const isEnvironmentMintRequestFailed = Schema.is(EnvironmentConnector.EnvironmentMintRequestFailed); +const isEnvironmentMintResponseInvalid = Schema.is( + EnvironmentConnector.EnvironmentMintResponseInvalid, +); function requestBodyText(request: HttpClientRequest.HttpClientRequest): string { return request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "{}"; @@ -700,7 +704,7 @@ describe("EnvironmentConnector", () => { return Effect.gen(function* () { const connector = yield* EnvironmentConnector.EnvironmentConnector; - const result = yield* Effect.exit( + const result = yield* Effect.result( connector.connect({ userId: "user_123", environmentId: "env-connector-test", @@ -708,9 +712,19 @@ describe("EnvironmentConnector", () => { }), ); - expect(result._tag).toBe("Failure"); - if (result._tag === "Failure") { - expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); + expect(Result.isFailure(result)).toBe(true); + if (Result.isFailure(result)) { + expect(isEnvironmentMintResponseInvalid(result.failure)).toBe(true); + if (isEnvironmentMintResponseInvalid(result.failure)) { + expect(result.failure).toMatchObject({ + userId: "user_123", + environmentId: "env-connector-test", + operation: "connect", + httpBaseUrl: "https://env.example.test/", + reason: "proof_verification_failed", + cause: { _tag: "RelayJwtError" }, + }); + } } }).pipe(Effect.provide(connectorTestLayer(execute))); }); @@ -780,6 +794,50 @@ describe("EnvironmentConnector", () => { }).pipe(Effect.provide(connectorTestLayer(execute))); }); + it.effect("preserves context and cause when the mint request fails", () => { + const execute = (request: HttpClientRequest.HttpClientRequest) => + Effect.succeed( + HttpClientResponse.fromWeb( + request, + Response.json( + { + _tag: "EnvironmentHttpInternalServerError", + message: "Environment is unavailable.", + }, + { status: 500 }, + ), + ), + ); + + return Effect.gen(function* () { + const connector = yield* EnvironmentConnector.EnvironmentConnector; + const result = yield* Effect.result( + connector.connect({ + userId: "user_123", + environmentId: "env-connector-test", + clientProofKeyThumbprint: "client-proof-key-thumbprint", + deviceId: "device-123", + }), + ); + + expect(Result.isFailure(result)).toBe(true); + if (Result.isFailure(result)) { + expect(isEnvironmentMintRequestFailed(result.failure)).toBe(true); + if (isEnvironmentMintRequestFailed(result.failure)) { + expect(result.failure).toMatchObject({ + userId: "user_123", + environmentId: "env-connector-test", + operation: "connect", + stage: "send_request", + httpBaseUrl: "https://env.example.test/", + deviceId: "device-123", + cause: { _tag: "EnvironmentHttpInternalServerError" }, + }); + } + } + }).pipe(Effect.provide(connectorTestLayer(execute))); + }); + it.effect("times out hung managed endpoint mint requests", () => { let resolveRequestStarted: (() => void) | undefined; const requestStarted = new Promise((resolve) => { @@ -810,7 +868,9 @@ describe("EnvironmentConnector", () => { if (Result.isFailure(result)) { expect(result.failure._tag).toBe("EnvironmentMintRequestTimedOut"); expect(result.failure).toMatchObject({ + userId: "user_123", environmentId: "env-connector-test", + httpBaseUrl: "https://env.example.test/", timeoutMs: EnvironmentConnector.ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS, }); } diff --git a/infra/relay/src/environments/EnvironmentConnector.ts b/infra/relay/src/environments/EnvironmentConnector.ts index db662aee94d..b78171cdd94 100644 --- a/infra/relay/src/environments/EnvironmentConnector.ts +++ b/infra/relay/src/environments/EnvironmentConnector.ts @@ -96,37 +96,67 @@ export class EnvironmentConnectNotAuthorized extends Schema.TaggedErrorClass()( "EnvironmentMintRequestFailed", { + userId: Schema.String, environmentId: Schema.String, operation: Schema.Literals(["connect", "status"]), + stage: Schema.Literals(["generate_nonce", "generate_request_id", "sign_proof", "send_request"]), + httpBaseUrl: Schema.String, + deviceId: Schema.optional(Schema.String), cause: Schema.Defect(), }, ) { override get message(): string { - return `Environment '${this.environmentId}' ${this.operation} request failed`; + return `Environment '${this.environmentId}' ${this.operation} request failed during ${this.stage}`; } } export class EnvironmentMintRequestTimedOut extends Schema.TaggedErrorClass()( "EnvironmentMintRequestTimedOut", { + userId: Schema.String, environmentId: Schema.String, + httpBaseUrl: Schema.String, + deviceId: Schema.optional(Schema.String), timeoutMs: Schema.Number, }, ) { override get message(): string { - return `Environment '${this.environmentId}' mint request timed out after ${this.timeoutMs}ms`; + return `Environment '${this.environmentId}' mint request to '${this.httpBaseUrl}' timed out after ${this.timeoutMs}ms`; } } +export const EnvironmentMintResponseInvalidReason = Schema.Literals([ + "environment_public_key_missing", + "proof_verification_failed", + "response_environment_id_mismatch", + "proof_environment_id_mismatch", + "request_nonce_mismatch", + "client_proof_key_thumbprint_mismatch", + "credential_mismatch", + "expires_at_invalid", + "expires_at_mismatch", + "status_mismatch", + "checked_at_invalid", + "checked_at_mismatch", + "descriptor_mismatch", + "checked_at_out_of_range", +]); +export type EnvironmentMintResponseInvalidReason = typeof EnvironmentMintResponseInvalidReason.Type; + export class EnvironmentMintResponseInvalid extends Schema.TaggedErrorClass()( "EnvironmentMintResponseInvalid", { + userId: Schema.String, environmentId: Schema.String, operation: Schema.Literals(["connect", "status"]), + httpBaseUrl: Schema.String, + deviceId: Schema.optional(Schema.String), + reason: EnvironmentMintResponseInvalidReason, + cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { - return `Environment '${this.environmentId}' returned an invalid ${this.operation} response`; + return `Environment '${this.environmentId}' returned an invalid ${this.operation} response (${this.reason})`; } } @@ -210,17 +240,24 @@ const verifyWithEnvironmentKeys = Effect.fnUntraced(function* (input: { readonly decodePayload: (input: unknown) => Effect.Effect; }) { const { decodePayload, ...rest } = input; + let lastFailure: { readonly cause: unknown } | undefined; for (const publicKey of input.environmentPublicKeys) { - const proof = yield* verifyRelayJwt({ ...rest, publicKey }).pipe( + const result = yield* verifyRelayJwt({ ...rest, publicKey }).pipe( Effect.flatMap(decodePayload), - Effect.option, + Effect.match({ + onFailure: (cause) => ({ _tag: "Failure" as const, cause }), + onSuccess: (proof) => ({ _tag: "Success" as const, proof }), + }), ); - if (Option.isSome(proof)) { - return proof.value; + if (result._tag === "Success") { + return result; } + lastFailure = result; // A linked environment can have rotated keys; try the remaining active keys. } - return null; + return lastFailure + ? { _tag: "Failure" as const, cause: lastFailure.cause } + : { _tag: "MissingPublicKey" as const }; }); function verifyEnvironmentResponse(input: { @@ -241,18 +278,35 @@ function verifyEnvironmentResponse(input: { environmentPublicKeys: input.environmentPublicKeys, decodePayload: decodeMintResponseProof, }).pipe( - Effect.map( - (proof) => - proof !== null && - proof.environmentId === input.environmentId && - proof.requestNonce === input.requestNonce && - proof.clientProofKeyThumbprint === input.clientProofKeyThumbprint && - proof.credential === input.response.credential && - Option.match(DateTime.make(input.response.expiresAt), { - onNone: () => false, - onSome: (expiresAt) => Math.floor(expiresAt.epochMilliseconds / 1_000) === proof.exp, - }), - ), + Effect.map((verification) => { + if (verification._tag === "MissingPublicKey") { + return { reason: "environment_public_key_missing" as const }; + } + if (verification._tag === "Failure") { + return { reason: "proof_verification_failed" as const, cause: verification.cause }; + } + const proof = verification.proof; + if (proof.environmentId !== input.environmentId) { + return { reason: "proof_environment_id_mismatch" as const }; + } + if (proof.requestNonce !== input.requestNonce) { + return { reason: "request_nonce_mismatch" as const }; + } + if (proof.clientProofKeyThumbprint !== input.clientProofKeyThumbprint) { + return { reason: "client_proof_key_thumbprint_mismatch" as const }; + } + if (proof.credential !== input.response.credential) { + return { reason: "credential_mismatch" as const }; + } + const expiresAt = DateTime.make(input.response.expiresAt); + if (Option.isNone(expiresAt)) { + return { reason: "expires_at_invalid" as const }; + } + if (Math.floor(expiresAt.value.epochMilliseconds / 1_000) !== proof.exp) { + return { reason: "expires_at_mismatch" as const }; + } + return null; + }), ); } @@ -274,28 +328,45 @@ function verifyEnvironmentHealthResponse(input: { environmentPublicKeys: input.environmentPublicKeys, decodePayload: decodeHealthResponseProof, }).pipe( - Effect.map((proof) => { - if ( - proof === null || - input.response.environmentId !== input.environmentId || - proof.environmentId !== input.environmentId || - proof.requestNonce !== input.requestNonce || - proof.status !== input.response.status || - proof.checkedAt !== input.response.checkedAt || - stableStringify(proof.descriptor) !== stableStringify(input.response.descriptor) - ) { - return false; + Effect.map((verification) => { + if (verification._tag === "MissingPublicKey") { + return { reason: "environment_public_key_missing" as const }; + } + if (verification._tag === "Failure") { + return { reason: "proof_verification_failed" as const, cause: verification.cause }; + } + const proof = verification.proof; + if (input.response.environmentId !== input.environmentId) { + return { reason: "response_environment_id_mismatch" as const }; + } + if (proof.environmentId !== input.environmentId) { + return { reason: "proof_environment_id_mismatch" as const }; + } + if (proof.requestNonce !== input.requestNonce) { + return { reason: "request_nonce_mismatch" as const }; + } + if (proof.status !== input.response.status) { + return { reason: "status_mismatch" as const }; + } + if (proof.checkedAt !== input.response.checkedAt) { + return { reason: "checked_at_mismatch" as const }; + } + if (stableStringify(proof.descriptor) !== stableStringify(input.response.descriptor)) { + return { reason: "descriptor_mismatch" as const }; } const checkedAt = DateTime.make(input.response.checkedAt); if (Option.isNone(checkedAt)) { - return false; + return { reason: "checked_at_invalid" as const }; } - return ( - checkedAt.value.epochMilliseconds >= - input.requestIssuedAt.epochMilliseconds - ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS && - checkedAt.value.epochMilliseconds <= + if ( + checkedAt.value.epochMilliseconds < + input.requestIssuedAt.epochMilliseconds - ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS || + checkedAt.value.epochMilliseconds > input.now.epochMilliseconds + ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS - ); + ) { + return { reason: "checked_at_out_of_range" as const }; + } + return null; }), ); } @@ -434,8 +505,11 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "status", + stage: "generate_nonce", + httpBaseUrl: endpoint.httpBaseUrl, cause, }), ), @@ -448,8 +522,11 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "status", + stage: "generate_request_id", + httpBaseUrl: endpoint.httpBaseUrl, cause, }), ), @@ -468,8 +545,11 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "status", + stage: "sign_proof", + httpBaseUrl: endpoint.httpBaseUrl, cause, }), ), @@ -527,7 +607,7 @@ const make = Effect.gen(function* () { }; } const decoded = responseOption.value.response; - const verified = yield* verifyEnvironmentHealthResponse({ + const invalidResponse = yield* verifyEnvironmentHealthResponse({ response: decoded, environmentId: input.environmentId, requestNonce: nonce, @@ -536,10 +616,14 @@ const make = Effect.gen(function* () { relayIssuer, now: yield* DateTime.now, }); - if (!verified) { + if (invalidResponse) { return yield* new EnvironmentMintResponseInvalid({ + userId: input.userId, environmentId: input.environmentId, operation: "status", + httpBaseUrl: endpoint.httpBaseUrl, + reason: invalidResponse.reason, + ...(invalidResponse.cause !== undefined ? { cause: invalidResponse.cause } : {}), }); } return { @@ -589,8 +673,12 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "connect", + stage: "generate_nonce", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), cause, }), ), @@ -603,8 +691,12 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "connect", + stage: "generate_request_id", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), cause, }), ), @@ -626,8 +718,12 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "connect", + stage: "sign_proof", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), cause, }), ), @@ -640,8 +736,12 @@ const make = Effect.gen(function* () { Effect.mapError( (cause) => new EnvironmentMintRequestFailed({ + userId: input.userId, environmentId: input.environmentId, operation: "connect", + stage: "send_request", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), cause, }), ), @@ -651,7 +751,10 @@ const make = Effect.gen(function* () { onNone: () => Effect.fail( new EnvironmentMintRequestTimedOut({ + userId: input.userId, environmentId: input.environmentId, + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), timeoutMs: ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS, }), ), @@ -659,7 +762,7 @@ const make = Effect.gen(function* () { }), ), ); - const verified = yield* verifyEnvironmentResponse({ + const invalidResponse = yield* verifyEnvironmentResponse({ response: decoded, environmentId: input.environmentId, requestNonce: nonce, @@ -668,10 +771,15 @@ const make = Effect.gen(function* () { relayIssuer, nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), }); - if (!verified) { + if (invalidResponse) { return yield* new EnvironmentMintResponseInvalid({ + userId: input.userId, environmentId: input.environmentId, operation: "connect", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), + reason: invalidResponse.reason, + ...(invalidResponse.cause !== undefined ? { cause: invalidResponse.cause } : {}), }); } return { From 66d795b3f4424bb3796e1db8b6f8cf36dca3b981 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 05:35:17 -0700 Subject: [PATCH 2/4] Refresh automated review Co-authored-by: codex From de7427d2ab709bbd7df69c43f8dc1176cd19a199 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 09:58:36 -0700 Subject: [PATCH 3/4] Redact relay environment endpoint diagnostics Co-authored-by: codex --- .../environments/EnvironmentConnector.test.ts | 106 +++++++- .../src/environments/EnvironmentConnector.ts | 250 ++++++++++++------ 2 files changed, 259 insertions(+), 97 deletions(-) diff --git a/infra/relay/src/environments/EnvironmentConnector.test.ts b/infra/relay/src/environments/EnvironmentConnector.test.ts index e4e318cc7e4..3cb51923994 100644 --- a/infra/relay/src/environments/EnvironmentConnector.test.ts +++ b/infra/relay/src/environments/EnvironmentConnector.test.ts @@ -231,6 +231,55 @@ function makeLinks( } describe("EnvironmentConnector", () => { + it("redacts endpoint secrets while preserving mapped causes", () => { + const httpBaseUrl = + "https://environment-user:environment-password@env.example.test/private/workspace?access_token=environment-secret#environment-fragment"; + const cause = new Error("mint failed"); + const requestError = EnvironmentConnector.EnvironmentMintRequestFailed.fromEndpoint({ + userId: "user_123", + environmentId: "env-connector-test", + operation: "connect", + stage: "send_request", + httpBaseUrl, + cause, + }); + const responseError = EnvironmentConnector.EnvironmentMintResponseInvalid.fromEndpoint({ + userId: "user_123", + environmentId: "env-connector-test", + operation: "connect", + httpBaseUrl, + reason: "proof_verification_failed", + cause, + }); + const timeoutError = EnvironmentConnector.EnvironmentMintRequestTimedOut.fromEndpoint({ + userId: "user_123", + environmentId: "env-connector-test", + httpBaseUrl, + timeoutMs: EnvironmentConnector.ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS, + }); + + for (const error of [requestError, responseError, timeoutError]) { + expect(error).toMatchObject({ + httpBaseUrlInputLength: httpBaseUrl.length, + httpBaseUrlProtocol: "https:", + httpBaseUrlHostname: "env.example.test", + }); + const serialized = JSON.stringify(error); + for (const secret of [ + "environment-user", + "environment-password", + "/private/workspace", + "environment-secret", + "environment-fragment", + ]) { + expect(serialized).not.toContain(secret); + expect(error.message).not.toContain(secret); + } + } + expect(requestError.cause).toBe(cause); + expect(responseError.cause).toBe(cause); + }); + it.effect("loads the environment link and managed allocation concurrently", () => Effect.gen(function* () { const started = yield* Ref.make(0); @@ -405,24 +454,55 @@ describe("EnvironmentConnector", () => { const resolutionSpan = spans.find( (span) => span.name === "relay.environment_connector.resolve_managed_endpoint", ); - expect(Object.fromEntries(resolutionSpan?.attributes ?? [])).toMatchObject({ + const resolutionAttributes = Object.fromEntries(resolutionSpan?.attributes ?? []); + expect(resolutionAttributes).toMatchObject({ "relay.authorization.allocation_hostname": "env.example.test", "relay.authorization.allocation_has_ready_at": true, "relay.authorization.allocation_has_tunnel_id": true, "relay.authorization.allocation_has_dns_record_id": true, - "relay.authorization.linked_http_base_url": "https://attacker.example.test/", - "relay.authorization.linked_ws_base_url": "wss://attacker.example.test/ws", - "relay.authorization.resolved_http_base_url": "https://env.example.test/", - "relay.authorization.resolved_ws_base_url": "wss://env.example.test/ws", + "relay.authorization.linked_http_base_url.input_length": + "https://attacker-user:attacker-password@attacker.example.test/private/workspace?access_token=attacker-secret#attacker-fragment" + .length, + "relay.authorization.linked_http_base_url.protocol": "https:", + "relay.authorization.linked_http_base_url.hostname": "attacker.example.test", + "relay.authorization.linked_ws_base_url.input_length": + "wss://socket-user:socket-password@attacker.example.test/private/socket?access_token=socket-secret#socket-fragment" + .length, + "relay.authorization.linked_ws_base_url.protocol": "wss:", + "relay.authorization.linked_ws_base_url.hostname": "attacker.example.test", + "relay.authorization.resolved_http_base_url.input_length": "https://env.example.test/" + .length, + "relay.authorization.resolved_http_base_url.protocol": "https:", + "relay.authorization.resolved_http_base_url.hostname": "env.example.test", + "relay.authorization.resolved_ws_base_url.input_length": "wss://env.example.test/ws".length, + "relay.authorization.resolved_ws_base_url.protocol": "wss:", + "relay.authorization.resolved_ws_base_url.hostname": "env.example.test", }); + const serializedAttributes = JSON.stringify(resolutionAttributes); + for (const secret of [ + "attacker-user", + "attacker-password", + "/private/workspace", + "attacker-secret", + "attacker-fragment", + "socket-user", + "socket-password", + "/private/socket", + "socket-secret", + "socket-fragment", + ]) { + expect(serializedAttributes).not.toContain(secret); + } expect(requestCount).toBe(0); }).pipe( Effect.provide( connectorTestLayer(execute, { links: makeLinks({ endpoint: { - httpBaseUrl: "https://attacker.example.test/", - wsBaseUrl: "wss://attacker.example.test/ws", + httpBaseUrl: + "https://attacker-user:attacker-password@attacker.example.test/private/workspace?access_token=attacker-secret#attacker-fragment", + wsBaseUrl: + "wss://socket-user:socket-password@attacker.example.test/private/socket?access_token=socket-secret#socket-fragment", providerKind: "cloudflare_tunnel", }, }), @@ -720,7 +800,9 @@ describe("EnvironmentConnector", () => { userId: "user_123", environmentId: "env-connector-test", operation: "connect", - httpBaseUrl: "https://env.example.test/", + httpBaseUrlInputLength: "https://env.example.test/".length, + httpBaseUrlProtocol: "https:", + httpBaseUrlHostname: "env.example.test", reason: "proof_verification_failed", cause: { _tag: "RelayJwtError" }, }); @@ -829,7 +911,9 @@ describe("EnvironmentConnector", () => { environmentId: "env-connector-test", operation: "connect", stage: "send_request", - httpBaseUrl: "https://env.example.test/", + httpBaseUrlInputLength: "https://env.example.test/".length, + httpBaseUrlProtocol: "https:", + httpBaseUrlHostname: "env.example.test", deviceId: "device-123", cause: { _tag: "EnvironmentHttpInternalServerError" }, }); @@ -870,7 +954,9 @@ describe("EnvironmentConnector", () => { expect(result.failure).toMatchObject({ userId: "user_123", environmentId: "env-connector-test", - httpBaseUrl: "https://env.example.test/", + httpBaseUrlInputLength: "https://env.example.test/".length, + httpBaseUrlProtocol: "https:", + httpBaseUrlHostname: "env.example.test", timeoutMs: EnvironmentConnector.ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS, }); } diff --git a/infra/relay/src/environments/EnvironmentConnector.ts b/infra/relay/src/environments/EnvironmentConnector.ts index b78171cdd94..5d2e551f973 100644 --- a/infra/relay/src/environments/EnvironmentConnector.ts +++ b/infra/relay/src/environments/EnvironmentConnector.ts @@ -26,6 +26,7 @@ import { verifyRelayJwt, } from "@t3tools/shared/relayJwt"; import { stableStringify } from "@t3tools/shared/relaySigning"; +import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; @@ -93,6 +94,30 @@ export class EnvironmentConnectNotAuthorized extends Schema.TaggedErrorClass()( "EnvironmentMintRequestFailed", { @@ -100,11 +125,27 @@ export class EnvironmentMintRequestFailed extends Schema.TaggedErrorClass - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "status", - stage: "generate_nonce", - httpBaseUrl: endpoint.httpBaseUrl, - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "status", + stage: "generate_nonce", + httpBaseUrl: endpoint.httpBaseUrl, + cause, + }), ), ); const payload = { @@ -519,16 +601,15 @@ const make = Effect.gen(function* () { aud: `t3-env:${link.environmentId}`, sub: input.userId, jti: yield* crypto.randomUUIDv4.pipe( - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "status", - stage: "generate_request_id", - httpBaseUrl: endpoint.httpBaseUrl, - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "status", + stage: "generate_request_id", + httpBaseUrl: endpoint.httpBaseUrl, + cause, + }), ), ), iat: Math.floor(now.epochMilliseconds / 1_000), @@ -542,16 +623,15 @@ const make = Effect.gen(function* () { typ: RELAY_HEALTH_REQUEST_TYP, payload, }).pipe( - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "status", - stage: "sign_proof", - httpBaseUrl: endpoint.httpBaseUrl, - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "status", + stage: "sign_proof", + httpBaseUrl: endpoint.httpBaseUrl, + cause, + }), ), ); const checkedAt = DateTime.formatIso(now); @@ -572,7 +652,7 @@ const make = Effect.gen(function* () { }); yield* Effect.logWarning("Managed endpoint health request timed out", { environmentId: link.environmentId, - endpoint: endpoint.httpBaseUrl, + ...httpBaseUrlDiagnosticFields(endpoint.httpBaseUrl), traceId, }); return { @@ -593,7 +673,7 @@ const make = Effect.gen(function* () { }); yield* Effect.logWarning("Managed endpoint health request failed", { environmentId: link.environmentId, - endpoint: endpoint.httpBaseUrl, + ...httpBaseUrlDiagnosticFields(endpoint.httpBaseUrl), failureReason, traceId, }); @@ -617,7 +697,7 @@ const make = Effect.gen(function* () { now: yield* DateTime.now, }); if (invalidResponse) { - return yield* new EnvironmentMintResponseInvalid({ + return yield* EnvironmentMintResponseInvalid.fromEndpoint({ userId: input.userId, environmentId: input.environmentId, operation: "status", @@ -670,17 +750,16 @@ const make = Effect.gen(function* () { const now = yield* DateTime.now; const expiresAt = DateTime.add(now, { minutes: 2 }); const nonce = yield* crypto.randomUUIDv4.pipe( - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "connect", - stage: "generate_nonce", - httpBaseUrl: endpoint.httpBaseUrl, - ...(input.deviceId ? { deviceId: input.deviceId } : {}), - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "connect", + stage: "generate_nonce", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), + cause, + }), ), ); const payload = { @@ -688,17 +767,16 @@ const make = Effect.gen(function* () { aud: `t3-env:${link.environmentId}`, sub: input.userId, jti: yield* crypto.randomUUIDv4.pipe( - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "connect", - stage: "generate_request_id", - httpBaseUrl: endpoint.httpBaseUrl, - ...(input.deviceId ? { deviceId: input.deviceId } : {}), - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "connect", + stage: "generate_request_id", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), + cause, + }), ), ), iat: Math.floor(now.epochMilliseconds / 1_000), @@ -715,17 +793,16 @@ const make = Effect.gen(function* () { typ: RELAY_MINT_REQUEST_TYP, payload, }).pipe( - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "connect", - stage: "sign_proof", - httpBaseUrl: endpoint.httpBaseUrl, - ...(input.deviceId ? { deviceId: input.deviceId } : {}), - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "connect", + stage: "sign_proof", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), + cause, + }), ), ); const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl); @@ -733,24 +810,23 @@ const make = Effect.gen(function* () { .t3MintCredential({ payload: { proof } }) .pipe( withoutRedirects, - Effect.mapError( - (cause) => - new EnvironmentMintRequestFailed({ - userId: input.userId, - environmentId: input.environmentId, - operation: "connect", - stage: "send_request", - httpBaseUrl: endpoint.httpBaseUrl, - ...(input.deviceId ? { deviceId: input.deviceId } : {}), - cause, - }), + Effect.mapError((cause) => + EnvironmentMintRequestFailed.fromEndpoint({ + userId: input.userId, + environmentId: input.environmentId, + operation: "connect", + stage: "send_request", + httpBaseUrl: endpoint.httpBaseUrl, + ...(input.deviceId ? { deviceId: input.deviceId } : {}), + cause, + }), ), Effect.timeoutOption(Duration.millis(ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS)), Effect.flatMap( Option.match({ onNone: () => Effect.fail( - new EnvironmentMintRequestTimedOut({ + EnvironmentMintRequestTimedOut.fromEndpoint({ userId: input.userId, environmentId: input.environmentId, httpBaseUrl: endpoint.httpBaseUrl, @@ -772,7 +848,7 @@ const make = Effect.gen(function* () { nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), }); if (invalidResponse) { - return yield* new EnvironmentMintResponseInvalid({ + return yield* EnvironmentMintResponseInvalid.fromEndpoint({ userId: input.userId, environmentId: input.environmentId, operation: "connect", From 86ee1b09604b5ff6eb85f2ef999760b714357614 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 10:14:34 -0700 Subject: [PATCH 4/4] Avoid raw JSON in relay diagnostics test Co-authored-by: codex --- infra/relay/src/environments/EnvironmentConnector.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infra/relay/src/environments/EnvironmentConnector.test.ts b/infra/relay/src/environments/EnvironmentConnector.test.ts index 3cb51923994..af8584e32c1 100644 --- a/infra/relay/src/environments/EnvironmentConnector.test.ts +++ b/infra/relay/src/environments/EnvironmentConnector.test.ts @@ -478,7 +478,9 @@ describe("EnvironmentConnector", () => { "relay.authorization.resolved_ws_base_url.protocol": "wss:", "relay.authorization.resolved_ws_base_url.hostname": "env.example.test", }); - const serializedAttributes = JSON.stringify(resolutionAttributes); + const serializedAttributes = Object.entries(resolutionAttributes) + .flatMap(([key, value]) => [key, String(value)]) + .join("\n"); for (const secret of [ "attacker-user", "attacker-password",