diff --git a/src/infra/diagnostic-trace-context.test.ts b/src/infra/diagnostic-trace-context.test.ts index 7e4cbb8612a..db98767055a 100644 --- a/src/infra/diagnostic-trace-context.test.ts +++ b/src/infra/diagnostic-trace-context.test.ts @@ -45,12 +45,27 @@ describe("diagnostic-trace-context", () => { it("rejects malformed traceparent values", () => { expect(parseDiagnosticTraceparent(undefined)).toBeUndefined(); + expect(parseDiagnosticTraceparent(`00-${TRACE_ID}-${SPAN_ID}-01-extra`)).toBeUndefined(); expect(parseDiagnosticTraceparent(`ff-${TRACE_ID}-${SPAN_ID}-01`)).toBeUndefined(); expect(parseDiagnosticTraceparent(`00-${"0".repeat(32)}-${SPAN_ID}-01`)).toBeUndefined(); expect(parseDiagnosticTraceparent(`00-${TRACE_ID}-${"0".repeat(16)}-01`)).toBeUndefined(); expect(parseDiagnosticTraceparent(`00-${TRACE_ID}-${SPAN_ID}-xyz`)).toBeUndefined(); }); + it("rejects oversized traceparent values before parsing", () => { + expect( + parseDiagnosticTraceparent(`00-${TRACE_ID}-${SPAN_ID}-01-${"a".repeat(128)}`), + ).toBeUndefined(); + }); + + it("continues future-version traceparents from the first four fields", () => { + expect(parseDiagnosticTraceparent(`01-${TRACE_ID}-${SPAN_ID}-01-extra`)).toEqual({ + traceId: TRACE_ID, + spanId: SPAN_ID, + traceFlags: "01", + }); + }); + it("creates a normalized context from explicit fields or traceparent", () => { expect( createDiagnosticTraceContext({ @@ -71,6 +86,14 @@ describe("diagnostic-trace-context", () => { }); }); + it("generates valid non-zero ids for fallback contexts", () => { + const context = createDiagnosticTraceContext(); + + expect(isValidDiagnosticTraceId(context.traceId)).toBe(true); + expect(isValidDiagnosticSpanId(context.spanId)).toBe(true); + expect(formatDiagnosticTraceparent(context)).toBeDefined(); + }); + it("creates child contexts without retaining parent references or self-parenting", () => { const parent = createDiagnosticTraceContext({ traceId: TRACE_ID, diff --git a/src/infra/diagnostic-trace-context.ts b/src/infra/diagnostic-trace-context.ts index 2a25cfb9522..c3eb11cbd37 100644 --- a/src/infra/diagnostic-trace-context.ts +++ b/src/infra/diagnostic-trace-context.ts @@ -2,9 +2,11 @@ import { randomBytes } from "node:crypto"; const TRACEPARENT_VERSION = "00"; const DEFAULT_TRACE_FLAGS = "01"; +const MAX_TRACEPARENT_LENGTH = 128; const TRACE_ID_RE = /^[0-9a-f]{32}$/; const SPAN_ID_RE = /^[0-9a-f]{16}$/; const TRACE_FLAGS_RE = /^[0-9a-f]{2}$/; +const TRACEPARENT_VERSION_RE = /^[0-9a-f]{2}$/; export type DiagnosticTraceContext = { /** W3C trace id, 32 lowercase hex chars. */ @@ -29,6 +31,22 @@ function isNonZeroHex(value: string): boolean { return !/^0+$/.test(value); } +function randomTraceId(): string { + let traceId = randomHex(16); + while (!isNonZeroHex(traceId)) { + traceId = randomHex(16); + } + return traceId; +} + +function randomSpanId(): string { + let spanId = randomHex(8); + while (!isNonZeroHex(spanId)) { + spanId = randomHex(8); + } + return spanId; +} + export function isValidDiagnosticTraceId(value: unknown): value is string { return typeof value === "string" && TRACE_ID_RE.test(value) && isNonZeroHex(value); } @@ -68,12 +86,19 @@ function normalizeTraceFlags(value: unknown): string | undefined { export function parseDiagnosticTraceparent( traceparent: string | undefined, ): DiagnosticTraceContext | undefined { - const parts = traceparent?.trim().toLowerCase().split("-"); - if (!parts || parts.length !== 4) { + if (typeof traceparent !== "string" || traceparent.length > MAX_TRACEPARENT_LENGTH) { + return undefined; + } + const parts = traceparent.trim().toLowerCase().split("-"); + if (!parts || parts.length < 4) { return undefined; } const [version, traceId, spanId, traceFlags] = parts; - if (version !== TRACEPARENT_VERSION) { + if ( + !TRACEPARENT_VERSION_RE.test(version) || + version === "ff" || + (version === TRACEPARENT_VERSION && parts.length !== 4) + ) { return undefined; } const normalizedTraceId = normalizeTraceId(traceId); @@ -108,8 +133,8 @@ export function createDiagnosticTraceContext( input: DiagnosticTraceContextInput = {}, ): DiagnosticTraceContext { const parsed = parseDiagnosticTraceparent(input.traceparent); - const traceId = normalizeTraceId(input.traceId) ?? parsed?.traceId ?? randomHex(16); - const spanId = normalizeSpanId(input.spanId) ?? parsed?.spanId ?? randomHex(8); + const traceId = normalizeTraceId(input.traceId) ?? parsed?.traceId ?? randomTraceId(); + const spanId = normalizeSpanId(input.spanId) ?? parsed?.spanId ?? randomSpanId(); const parentSpanId = normalizeSpanId(input.parentSpanId); return { traceId,