fix(shared): keep valid OTEL headers when one entry is malformed (#12260)

parseKeyPairsIntoRecord wrapped the whole forEach in one try/catch, so a single entry that broke decodeURIComponent (e.g. a stray % in OTEL_EXPORTER_OTLP_HEADERS) aborted the loop and silently dropped every remaining header. Move the try/catch inside the loop to skip only the malformed entry. Adds regression tests.
This commit is contained in:
Edoardo Busano
2026-07-30 00:47:44 +02:00
committed by GitHub
parent c5661f8835
commit 704e953b69
2 changed files with 59 additions and 6 deletions
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { parseKeyPairsIntoRecord } from "./utils";
describe("parseKeyPairsIntoRecord", () => {
it("parses comma-separated key=value pairs", () => {
expect(parseKeyPairsIntoRecord("a=1,b=2,c=3")).toEqual({
a: "1",
b: "2",
c: "3",
});
});
it("returns an empty record for empty or undefined input", () => {
expect(parseKeyPairsIntoRecord("")).toEqual({});
expect(parseKeyPairsIntoRecord(undefined)).toEqual({});
});
it("trims whitespace and decodes percent-encoded keys and values", () => {
expect(parseKeyPairsIntoRecord(" Authorization = Bearer%20abc ")).toEqual({
Authorization: "Bearer abc",
});
});
it("skips entries without a separator or with an empty key", () => {
expect(parseKeyPairsIntoRecord("novalue,=nokey,good=1")).toEqual({
good: "1",
});
});
it("keeps well-formed entries when a later entry is malformed", () => {
// `b=%` is an invalid percent-encoding: decodeURIComponent throws on it.
// The malformed entry must be skipped without discarding `a` or `c`.
expect(parseKeyPairsIntoRecord("a=1,b=%,c=3")).toEqual({
a: "1",
c: "3",
});
});
it("skips only the malformed pair in a realistic OTEL header string", () => {
expect(
parseKeyPairsIntoRecord(
"Authorization=Bearer abc,X-Tenant=acme%,X-Trace=on",
),
).toEqual({
Authorization: "Bearer abc",
"X-Trace": "on",
});
});
});
+10 -6
View File
@@ -7,11 +7,11 @@ export function parseKeyPairsIntoRecord(
return result;
}
try {
value.split(",").forEach((entry) => {
const separatorIndex = entry.indexOf("=");
if (separatorIndex <= 0) return;
value.split(",").forEach((entry) => {
const separatorIndex = entry.indexOf("=");
if (separatorIndex <= 0) return;
try {
// From the beginning to the equal sign is the key, from the equal sign to the end is the value
const key = decodeURIComponent(entry.substring(0, separatorIndex).trim());
const value = decodeURIComponent(
@@ -21,8 +21,12 @@ export function parseKeyPairsIntoRecord(
if (!key || !value) return;
result[key] = value;
});
} catch {}
} catch {
// Skip a single malformed entry (e.g. an invalid percent-encoding
// that makes decodeURIComponent throw) rather than aborting the
// whole list and silently dropping every remaining pair.
}
});
return result;
}