This is an automated email from the ASF dual-hosted git repository.
hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 1fb58a67d fix(node): reject malformed user headers before sending
(#4764)
1fb58a67d is described below
commit 1fb58a67d382b9a997573ab04d102565362b8afa
Author: Subham <[email protected]>
AuthorDate: Sat Nov 7 13:23:04 2026 +0530
fix(node): reject malformed user headers before sending (#4764)
---
foreign/node/src/wire/message/header.utils.test.ts | 56 ++++++++++++++++++++++
foreign/node/src/wire/message/header.utils.ts | 16 +++++++
2 files changed, 72 insertions(+)
diff --git a/foreign/node/src/wire/message/header.utils.test.ts
b/foreign/node/src/wire/message/header.utils.test.ts
index 590c21f12..0554a7fd1 100644
--- a/foreign/node/src/wire/message/header.utils.test.ts
+++ b/foreign/node/src/wire/message/header.utils.test.ts
@@ -25,6 +25,7 @@ import {
HeaderKeyFactory,
} from "./header.utils.js";
import { HeaderKind } from "./header.type.js";
+import { serializeSendMessages } from "./message.utils.js";
describe("Headers", () => {
const headers = [
@@ -66,3 +67,58 @@ describe("Headers", () => {
assert.equal(d[0].value.value, "test");
});
});
+
+
+describe("header encoding validation", () => {
+ const key = HeaderKeyFactory.String("key");
+ const value = HeaderValue.String("value");
+ const invalid = [
+ { name: "empty string key", key: HeaderKeyFactory.String(""), value },
+ { name: "empty raw key", key: HeaderKeyFactory.Raw(Buffer.alloc(0)), value
},
+ { name: "256-byte string key", key:
HeaderKeyFactory.String("a".repeat(256)), value },
+ { name: "256-byte raw key", key: HeaderKeyFactory.Raw(Buffer.alloc(256)),
value },
+ { name: "256-byte UTF-8 key", key:
HeaderKeyFactory.String("é".repeat(128)), value },
+ { name: "empty string value", key, value: HeaderValue.String("") },
+ { name: "empty raw value", key, value: HeaderValue.Raw(Buffer.alloc(0)) },
+ { name: "256-byte string value", key, value:
HeaderValue.String("a".repeat(256)) },
+ { name: "256-byte raw value", key, value:
HeaderValue.Raw(Buffer.alloc(256)) },
+ { name: "256-byte UTF-8 value", key, value:
HeaderValue.String("é".repeat(128)) },
+ ...[0, 1, 15, 17].flatMap((length) => [
+ { name: `${length}-byte Int128`, key, value:
HeaderValue.Int128(Buffer.alloc(length)) },
+ { name: `${length}-byte Uint128`, key, value:
HeaderValue.Uint128(Buffer.alloc(length)) },
+ ]),
+ ];
+
+ for (const { name, ...header } of invalid) {
+ it(`rejects ${name} before encoding a message batch`, () => {
+ assert.throws(() => serializeHeaders([header]), /Invalid header/);
+ assert.throws(() => serializeSendMessages(1, 1, [
+ { id: 1n, payload: "test", headers: [header] },
+ ]), /Invalid header/);
+ });
+ }
+
+ it("round-trips valid byte-length boundaries and fixed-width values", () => {
+ const headers = [
+ { key: HeaderKeyFactory.Raw(Buffer.from([1])), value:
HeaderValue.Raw(Buffer.from([1])) },
+ { key: HeaderKeyFactory.Raw(Buffer.alloc(255)), value:
HeaderValue.Raw(Buffer.alloc(255)) },
+ { key: HeaderKeyFactory.String("a".repeat(255)), value:
HeaderValue.String("a".repeat(255)) },
+ { key: HeaderKeyFactory.String("é".repeat(127) + "a"), value:
HeaderValue.String("é".repeat(127) + "a") },
+ { key: HeaderKeyFactory.Int32(-1), value:
HeaderValue.Int128(Buffer.alloc(16, 255)) },
+ { key, value: HeaderValue.Uint128(Buffer.alloc(16, 255)) },
+ ];
+ assert.deepEqual(deserializeHeaders(serializeHeaders(headers)), headers);
+ assert.ok(serializeSendMessages(1, 1, [{ id: 1n, payload: "test", headers
}]).length > 0);
+ });
+
+ it("preserves the existing wire encoding of a valid header", () => {
+ assert.equal(serializeHeaders([{ key, value }]).toString("hex"),
+ "02030000006b6579020500000076616c7565");
+ });
+
+ it("allows a message without user headers", () => {
+ assert.deepEqual(serializeHeaders(), Buffer.alloc(0));
+ assert.deepEqual(serializeHeaders([]), Buffer.alloc(0));
+ assert.ok(serializeSendMessages(1, 1, [{ id: 1n, payload: "test"
}]).length > 0);
+ });
+});
diff --git a/foreign/node/src/wire/message/header.utils.ts
b/foreign/node/src/wire/message/header.utils.ts
index c31d45b43..8d068f85d 100644
--- a/foreign/node/src/wire/message/header.utils.ts
+++ b/foreign/node/src/wire/message/header.utils.ts
@@ -165,6 +165,22 @@ export const serializeHeaderKey = (key: HeaderKey): Buffer
=> {
*/
export const serializeHeader = (key: HeaderKey, v: BinaryHeaderValue) => {
const bKey = serializeHeaderKey(key);
+ for (const [field, kind, bytes] of [
+ ["key", key.kind, bKey],
+ ["value", v.kind, v.value],
+ ] as const) {
+ if (bytes.length < 1 || bytes.length > 255) {
+ throw new Error(
+ `Invalid header ${field} length: ${bytes.length}, must be between 1
and 255`,
+ );
+ }
+ const size = expectedSize(kind);
+ if (size !== -1 && bytes.length !== size) {
+ throw new Error(
+ `Invalid header ${field} size for kind ${kind}: expected ${size}, got
${bytes.length}`,
+ );
+ }
+ }
const keyHeader = Buffer.alloc(5);
keyHeader.writeUInt8(key.kind);
keyHeader.writeUInt32LE(bKey.length, 1);