mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:40:44 +00:00
fix(diagnostics): harden trace context parsing (#70955)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user