From f352685555ee679a3b2b3ec4a40b32477189c90d Mon Sep 17 00:00:00 2001 From: shiavm006 Date: Thu, 26 Feb 2026 13:45:55 +0530 Subject: [PATCH 1/5] feat(js): add configurable size guardrails --- javascript/packages/fory/lib/fory.ts | 4 ++++ javascript/packages/fory/lib/gen/collection.ts | 9 +++++++++ javascript/packages/fory/lib/gen/map.ts | 9 +++++++++ javascript/packages/fory/lib/reader/index.ts | 6 ++++++ javascript/packages/fory/lib/type.ts | 3 +++ 5 files changed, 31 insertions(+) diff --git a/javascript/packages/fory/lib/fory.ts b/javascript/packages/fory/lib/fory.ts index d26b6b6fa7..99ae2a5fa6 100644 --- a/javascript/packages/fory/lib/fory.ts +++ b/javascript/packages/fory/lib/fory.ts @@ -55,10 +55,14 @@ export default class { private initConfig(config: Partial | undefined) { return { + hps: config?.hps, refTracking: config?.refTracking !== null ? Boolean(config?.refTracking) : null, useSliceString: Boolean(config?.useSliceString), hooks: config?.hooks || {}, compatible: Boolean(config?.compatible), + maxStringBytes: config?.maxStringBytes, + maxCollectionLength: config?.maxCollectionLength, + maxMapEntries: config?.maxMapEntries, }; } diff --git a/javascript/packages/fory/lib/gen/collection.ts b/javascript/packages/fory/lib/gen/collection.ts index 9278b6dcc0..88f5246ee3 100644 --- a/javascript/packages/fory/lib/gen/collection.ts +++ b/javascript/packages/fory/lib/gen/collection.ts @@ -145,6 +145,10 @@ class CollectionAnySerializer { read(accessor: (result: any, index: number, v: any) => void, createCollection: (len: number) => any, fromRef: boolean): any { void fromRef; const len = this.fory.binaryReader.readVarUint32Small7(); + const maxLen = this.fory.config.maxCollectionLength; + if (typeof maxLen === "number" && maxLen > 0 && len > maxLen) { + throw new Error(`Collection length ${len} exceeds configured maxCollectionLength ${maxLen}`); + } const flags = this.fory.binaryReader.readUint8(); const isSame = flags & CollectionFlags.SAME_TYPE; const includeNone = flags & CollectionFlags.HAS_NULL; @@ -295,8 +299,13 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera const flags = this.scope.uniqueName("flags"); const idx = this.scope.uniqueName("idx"); const refFlag = this.scope.uniqueName("refFlag"); + const foryName = this.builder.getForyName(); return ` const ${len} = ${this.builder.reader.readVarUint32Small7()}; + const maxLen = ${foryName}.config.maxCollectionLength; + if (typeof maxLen === "number" && maxLen > 0 && ${len} > maxLen) { + throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength \${maxLen}\`); + } const ${flags} = ${this.builder.reader.readUint8()}; const ${result} = ${this.newCollection(len)}; ${this.maybeReference(result, refState)} diff --git a/javascript/packages/fory/lib/gen/map.ts b/javascript/packages/fory/lib/gen/map.ts index d94f05606a..76e9c77cca 100644 --- a/javascript/packages/fory/lib/gen/map.ts +++ b/javascript/packages/fory/lib/gen/map.ts @@ -235,6 +235,10 @@ class MapAnySerializer { read(fromRef: boolean): any { let count = this.fory.binaryReader.readVarUint32Small7(); + const maxEntries = this.fory.config.maxMapEntries; + if (typeof maxEntries === "number" && maxEntries > 0 && count > maxEntries) { + throw new Error(`Map entry count ${count} exceeds configured maxMapEntries ${maxEntries}`); + } const result = new Map(); if (fromRef) { this.fory.referenceResolver.reference(result); @@ -400,8 +404,13 @@ export class MapSerializerGenerator extends BaseSerializerGenerator { const count = this.scope.uniqueName("count"); const result = this.scope.uniqueName("result"); + const foryName = this.builder.getForyName(); return ` let ${count} = ${this.builder.reader.readVarUint32Small7()}; + const maxEntries = ${foryName}.config.maxMapEntries; + if (typeof maxEntries === "number" && maxEntries > 0 && ${count} > maxEntries) { + throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries \${maxEntries}\`); + } const ${result} = new Map(); if (${refState}) { ${this.builder.referenceResolver.reference(result)} diff --git a/javascript/packages/fory/lib/reader/index.ts b/javascript/packages/fory/lib/reader/index.ts index 4300bc9c52..015949873f 100644 --- a/javascript/packages/fory/lib/reader/index.ts +++ b/javascript/packages/fory/lib/reader/index.ts @@ -30,11 +30,14 @@ export class BinaryReader { private platformBuffer!: PlatformBuffer; private bigString = ""; private byteLength = 0; + private maxStringBytes?: number; constructor(config: { useSliceString?: boolean; + maxStringBytes?: number; }) { this.sliceStringEnable = isNodeEnv && config.useSliceString; + this.maxStringBytes = config.maxStringBytes; } reset(ab: Uint8Array) { @@ -189,6 +192,9 @@ export class BinaryReader { const header = this.readVarUint36Small(); const type = header & 0b11; const len = header >>> 2; + if (typeof this.maxStringBytes === "number" && this.maxStringBytes > 0 && len > this.maxStringBytes) { + throw new Error(`String byte length ${len} exceeds configured maxStringBytes ${this.maxStringBytes}`); + } switch (type) { case LATIN1: return this.stringLatin1(len); diff --git a/javascript/packages/fory/lib/type.ts b/javascript/packages/fory/lib/type.ts index 983c4797ce..d2cd1daa91 100644 --- a/javascript/packages/fory/lib/type.ts +++ b/javascript/packages/fory/lib/type.ts @@ -268,6 +268,9 @@ export interface Config { afterCodeGenerated?: (code: string) => string; }; compatible?: boolean; + maxStringBytes?: number; + maxCollectionLength?: number; + maxMapEntries?: number; } export interface WithForyClsInfo { From 5f3702e189c1cb8f83f30768d064287b5baba11f Mon Sep 17 00:00:00 2001 From: shiavm006 Date: Thu, 26 Feb 2026 14:13:02 +0530 Subject: [PATCH 2/5] test added --- javascript/test/array.test.ts | 16 ++++++++++++++++ javascript/test/map.test.ts | 10 ++++++++++ javascript/test/string.test.ts | 12 ++++++++++++ 3 files changed, 38 insertions(+) diff --git a/javascript/test/array.test.ts b/javascript/test/array.test.ts index 0abbd571a7..3f41eee1f3 100644 --- a/javascript/test/array.test.ts +++ b/javascript/test/array.test.ts @@ -148,6 +148,22 @@ describe('array', () => { expect(result.a7[1].toFloat32()).toBeCloseTo(-2.5, 2); expect(result.a7[2].toFloat32()).toBe(0); }); + + test('should enforce maxCollectionLength for list field', () => { + const typeinfo = Type.struct({ + typeName: "example.limit" + }, { + items: Type.array(Type.string()), + }); + const fory = new Fory({ refTracking: true, maxCollectionLength: 2 }); + const { serialize, deserialize } = fory.registerSerializer(typeinfo); + + const okBin = serialize({ items: ["a", "b"] }); + expect(deserialize(okBin)).toEqual({ items: ["a", "b"] }); + + const tooManyBin = serialize({ items: ["a", "b", "c"] }); + expect(() => deserialize(tooManyBin)).toThrow(/maxCollectionLength/); + }); }); diff --git a/javascript/test/map.test.ts b/javascript/test/map.test.ts index 0dae5ce947..64d574b922 100644 --- a/javascript/test/map.test.ts +++ b/javascript/test/map.test.ts @@ -43,6 +43,16 @@ describe('map', () => { const result = deserialize(bin); expect(result).toEqual({ f1: new Map([["hello", 123],["world", 456]])}) }); + + test('should enforce maxMapEntries when configured', () => { + const fory = new Fory({ refTracking: true, maxMapEntries: 1 }); + + const okInput = fory.serialize(new Map([["foo", "bar"]])); + expect(fory.deserialize(okInput)).toEqual(new Map([["foo", "bar"]])); + + const tooManyInput = fory.serialize(new Map([["foo", "bar"], ["baz", "qux"]])); + expect(() => fory.deserialize(tooManyInput)).toThrow(/maxMapEntries/); + }); }); diff --git a/javascript/test/string.test.ts b/javascript/test/string.test.ts index 1bcfe951f9..63e9025c71 100644 --- a/javascript/test/string.test.ts +++ b/javascript/test/string.test.ts @@ -62,4 +62,16 @@ describe('string', () => { ); expect(result).toEqual(str) }); + + test('should enforce maxStringBytes when configured', () => { + const short = "abcd"; + const long = "abcdef"; + const fory = new Fory({ ...config, maxStringBytes: 4 }); + + const ok = fory.serialize(short); + expect(fory.deserialize(ok)).toEqual(short); + + const tooLong = fory.serialize(long); + expect(() => fory.deserialize(tooLong)).toThrow(/maxStringBytes/); + }); }); From f035296b3d0b76813cf4dbadf6403d565d9d608e Mon Sep 17 00:00:00 2001 From: shiavm006 Date: Thu, 26 Feb 2026 15:57:16 +0530 Subject: [PATCH 3/5] ci fixed --- javascript/packages/fory/lib/gen/collection.ts | 7 ++++--- javascript/packages/fory/lib/gen/map.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/javascript/packages/fory/lib/gen/collection.ts b/javascript/packages/fory/lib/gen/collection.ts index 88f5246ee3..f156d785c9 100644 --- a/javascript/packages/fory/lib/gen/collection.ts +++ b/javascript/packages/fory/lib/gen/collection.ts @@ -300,11 +300,12 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera const idx = this.scope.uniqueName("idx"); const refFlag = this.scope.uniqueName("refFlag"); const foryName = this.builder.getForyName(); + const maxLenVar = this.scope.uniqueName("maxCollectionLength"); return ` const ${len} = ${this.builder.reader.readVarUint32Small7()}; - const maxLen = ${foryName}.config.maxCollectionLength; - if (typeof maxLen === "number" && maxLen > 0 && ${len} > maxLen) { - throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength \${maxLen}\`); + const ${maxLenVar} = ${foryName}.config.maxCollectionLength; + if (typeof ${maxLenVar} === "number" && ${maxLenVar} > 0 && ${len} > ${maxLenVar}) { + throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength \${${maxLenVar}}\`); } const ${flags} = ${this.builder.reader.readUint8()}; const ${result} = ${this.newCollection(len)}; diff --git a/javascript/packages/fory/lib/gen/map.ts b/javascript/packages/fory/lib/gen/map.ts index 76e9c77cca..74fbbc7547 100644 --- a/javascript/packages/fory/lib/gen/map.ts +++ b/javascript/packages/fory/lib/gen/map.ts @@ -405,11 +405,12 @@ export class MapSerializerGenerator extends BaseSerializerGenerator { const result = this.scope.uniqueName("result"); const foryName = this.builder.getForyName(); + const maxEntriesVar = this.scope.uniqueName("maxMapEntries"); return ` let ${count} = ${this.builder.reader.readVarUint32Small7()}; - const maxEntries = ${foryName}.config.maxMapEntries; - if (typeof maxEntries === "number" && maxEntries > 0 && ${count} > maxEntries) { - throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries \${maxEntries}\`); + const ${maxEntriesVar} = ${foryName}.config.maxMapEntries; + if (typeof ${maxEntriesVar} === "number" && ${maxEntriesVar} > 0 && ${count} > ${maxEntriesVar}) { + throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries \${${maxEntriesVar}}\`); } const ${result} = new Map(); if (${refState}) { From 4686cd9bef0c5dd166ee98e8052dcffbdd9cf05d Mon Sep 17 00:00:00 2001 From: shiavm006 Date: Mon, 2 Mar 2026 23:29:36 +0530 Subject: [PATCH 4/5] perf(js): inline size guardrails in codegen --- javascript/packages/fory/lib/gen/collection.ts | 15 +++++++++------ javascript/packages/fory/lib/gen/map.ts | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/javascript/packages/fory/lib/gen/collection.ts b/javascript/packages/fory/lib/gen/collection.ts index f156d785c9..e141d34c3f 100644 --- a/javascript/packages/fory/lib/gen/collection.ts +++ b/javascript/packages/fory/lib/gen/collection.ts @@ -299,14 +299,17 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera const flags = this.scope.uniqueName("flags"); const idx = this.scope.uniqueName("idx"); const refFlag = this.scope.uniqueName("refFlag"); - const foryName = this.builder.getForyName(); - const maxLenVar = this.scope.uniqueName("maxCollectionLength"); + const maxLenConfig = this.builder.fory.config.maxCollectionLength; + const limitCheck = typeof maxLenConfig === "number" && maxLenConfig > 0 + ? ` + if (${len} > ${maxLenConfig}) { + throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength ${maxLenConfig}\`); + } + ` + : ""; return ` const ${len} = ${this.builder.reader.readVarUint32Small7()}; - const ${maxLenVar} = ${foryName}.config.maxCollectionLength; - if (typeof ${maxLenVar} === "number" && ${maxLenVar} > 0 && ${len} > ${maxLenVar}) { - throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength \${${maxLenVar}}\`); - } + ${limitCheck} const ${flags} = ${this.builder.reader.readUint8()}; const ${result} = ${this.newCollection(len)}; ${this.maybeReference(result, refState)} diff --git a/javascript/packages/fory/lib/gen/map.ts b/javascript/packages/fory/lib/gen/map.ts index 74fbbc7547..c59baa6332 100644 --- a/javascript/packages/fory/lib/gen/map.ts +++ b/javascript/packages/fory/lib/gen/map.ts @@ -404,14 +404,17 @@ export class MapSerializerGenerator extends BaseSerializerGenerator { const count = this.scope.uniqueName("count"); const result = this.scope.uniqueName("result"); - const foryName = this.builder.getForyName(); - const maxEntriesVar = this.scope.uniqueName("maxMapEntries"); + const maxEntriesConfig = this.builder.fory.config.maxMapEntries; + const limitCheck = typeof maxEntriesConfig === "number" && maxEntriesConfig > 0 + ? ` + if (${count} > ${maxEntriesConfig}) { + throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries ${maxEntriesConfig}\`); + } + ` + : ""; return ` let ${count} = ${this.builder.reader.readVarUint32Small7()}; - const ${maxEntriesVar} = ${foryName}.config.maxMapEntries; - if (typeof ${maxEntriesVar} === "number" && ${maxEntriesVar} > 0 && ${count} > ${maxEntriesVar}) { - throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries \${${maxEntriesVar}}\`); - } + ${limitCheck} const ${result} = new Map(); if (${refState}) { ${this.builder.referenceResolver.reference(result)} From bb9ef436882488c239adba896f47ee28c717071c Mon Sep 17 00:00:00 2001 From: shiavm006 Date: Fri, 6 Mar 2026 17:35:28 +0530 Subject: [PATCH 5/5] feat(js): add configurable size guardrails --- javascript/packages/fory/lib/fory.ts | 4 ++-- javascript/packages/fory/lib/gen/collection.ts | 14 +++++++------- javascript/packages/fory/lib/reader/index.ts | 10 +++++----- javascript/packages/fory/lib/type.ts | 4 ++-- javascript/test/array.test.ts | 6 +++--- javascript/test/string.test.ts | 6 +++--- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/javascript/packages/fory/lib/fory.ts b/javascript/packages/fory/lib/fory.ts index 99ae2a5fa6..c0f4cf984b 100644 --- a/javascript/packages/fory/lib/fory.ts +++ b/javascript/packages/fory/lib/fory.ts @@ -60,8 +60,8 @@ export default class { useSliceString: Boolean(config?.useSliceString), hooks: config?.hooks || {}, compatible: Boolean(config?.compatible), - maxStringBytes: config?.maxStringBytes, - maxCollectionLength: config?.maxCollectionLength, + maxBinarySize: config?.maxBinarySize, + maxCollectionSize: config?.maxCollectionSize, maxMapEntries: config?.maxMapEntries, }; } diff --git a/javascript/packages/fory/lib/gen/collection.ts b/javascript/packages/fory/lib/gen/collection.ts index e141d34c3f..c34502c58a 100644 --- a/javascript/packages/fory/lib/gen/collection.ts +++ b/javascript/packages/fory/lib/gen/collection.ts @@ -145,9 +145,9 @@ class CollectionAnySerializer { read(accessor: (result: any, index: number, v: any) => void, createCollection: (len: number) => any, fromRef: boolean): any { void fromRef; const len = this.fory.binaryReader.readVarUint32Small7(); - const maxLen = this.fory.config.maxCollectionLength; - if (typeof maxLen === "number" && maxLen > 0 && len > maxLen) { - throw new Error(`Collection length ${len} exceeds configured maxCollectionLength ${maxLen}`); + const maxSize = this.fory.config.maxCollectionSize; + if (typeof maxSize === "number" && maxSize > 0 && len > maxSize) { + throw new Error(`Collection length ${len} exceeds configured maxCollectionSize ${maxSize}`); } const flags = this.fory.binaryReader.readUint8(); const isSame = flags & CollectionFlags.SAME_TYPE; @@ -299,11 +299,11 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera const flags = this.scope.uniqueName("flags"); const idx = this.scope.uniqueName("idx"); const refFlag = this.scope.uniqueName("refFlag"); - const maxLenConfig = this.builder.fory.config.maxCollectionLength; - const limitCheck = typeof maxLenConfig === "number" && maxLenConfig > 0 + const maxSizeConfig = this.builder.fory.config.maxCollectionSize; + const limitCheck = typeof maxSizeConfig === "number" && maxSizeConfig > 0 ? ` - if (${len} > ${maxLenConfig}) { - throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionLength ${maxLenConfig}\`); + if (${len} > ${maxSizeConfig}) { + throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionSize ${maxSizeConfig}\`); } ` : ""; diff --git a/javascript/packages/fory/lib/reader/index.ts b/javascript/packages/fory/lib/reader/index.ts index 015949873f..47ef342ffd 100644 --- a/javascript/packages/fory/lib/reader/index.ts +++ b/javascript/packages/fory/lib/reader/index.ts @@ -30,14 +30,14 @@ export class BinaryReader { private platformBuffer!: PlatformBuffer; private bigString = ""; private byteLength = 0; - private maxStringBytes?: number; + private maxBinarySize?: number; constructor(config: { useSliceString?: boolean; - maxStringBytes?: number; + maxBinarySize?: number; }) { this.sliceStringEnable = isNodeEnv && config.useSliceString; - this.maxStringBytes = config.maxStringBytes; + this.maxBinarySize = config.maxBinarySize; } reset(ab: Uint8Array) { @@ -192,8 +192,8 @@ export class BinaryReader { const header = this.readVarUint36Small(); const type = header & 0b11; const len = header >>> 2; - if (typeof this.maxStringBytes === "number" && this.maxStringBytes > 0 && len > this.maxStringBytes) { - throw new Error(`String byte length ${len} exceeds configured maxStringBytes ${this.maxStringBytes}`); + if (typeof this.maxBinarySize === "number" && this.maxBinarySize > 0 && len > this.maxBinarySize) { + throw new Error(`String byte length ${len} exceeds configured maxBinarySize ${this.maxBinarySize}`); } switch (type) { case LATIN1: diff --git a/javascript/packages/fory/lib/type.ts b/javascript/packages/fory/lib/type.ts index d2cd1daa91..ea62754404 100644 --- a/javascript/packages/fory/lib/type.ts +++ b/javascript/packages/fory/lib/type.ts @@ -268,8 +268,8 @@ export interface Config { afterCodeGenerated?: (code: string) => string; }; compatible?: boolean; - maxStringBytes?: number; - maxCollectionLength?: number; + maxBinarySize?: number; + maxCollectionSize?: number; maxMapEntries?: number; } diff --git a/javascript/test/array.test.ts b/javascript/test/array.test.ts index 3f41eee1f3..e96578e39c 100644 --- a/javascript/test/array.test.ts +++ b/javascript/test/array.test.ts @@ -149,20 +149,20 @@ describe('array', () => { expect(result.a7[2].toFloat32()).toBe(0); }); - test('should enforce maxCollectionLength for list field', () => { + test('should enforce maxCollectionSize for list field', () => { const typeinfo = Type.struct({ typeName: "example.limit" }, { items: Type.array(Type.string()), }); - const fory = new Fory({ refTracking: true, maxCollectionLength: 2 }); + const fory = new Fory({ refTracking: true, maxCollectionSize: 2 }); const { serialize, deserialize } = fory.registerSerializer(typeinfo); const okBin = serialize({ items: ["a", "b"] }); expect(deserialize(okBin)).toEqual({ items: ["a", "b"] }); const tooManyBin = serialize({ items: ["a", "b", "c"] }); - expect(() => deserialize(tooManyBin)).toThrow(/maxCollectionLength/); + expect(() => deserialize(tooManyBin)).toThrow(/maxCollectionSize/); }); }); diff --git a/javascript/test/string.test.ts b/javascript/test/string.test.ts index 63e9025c71..500f5bffa6 100644 --- a/javascript/test/string.test.ts +++ b/javascript/test/string.test.ts @@ -63,15 +63,15 @@ describe('string', () => { expect(result).toEqual(str) }); - test('should enforce maxStringBytes when configured', () => { + test('should enforce maxBinarySize when configured', () => { const short = "abcd"; const long = "abcdef"; - const fory = new Fory({ ...config, maxStringBytes: 4 }); + const fory = new Fory({ ...config, maxBinarySize: 4 }); const ok = fory.serialize(short); expect(fory.deserialize(ok)).toEqual(short); const tooLong = fory.serialize(long); - expect(() => fory.deserialize(tooLong)).toThrow(/maxStringBytes/); + expect(() => fory.deserialize(tooLong)).toThrow(/maxBinarySize/); }); });