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);

Reply via email to