From c4f801b1297afaf010b09cf406b3a7d0933fc89a Mon Sep 17 00:00:00 2001 From: Yarchik Date: Mon, 22 Jun 2026 21:46:52 +0100 Subject: [PATCH] fix: reject array indices with a leading zero (RFC 6901) isInteger() only checked that every character is a digit, so it accepted malformed array indices with a leading zero such as "01" or "007". Those are not valid JSON Pointer array indices (RFC 6901 section 4: "0", or a digit 1-9 followed by digits). As a result `add` (and move/copy destinations) accepted them and coerced "01" to index 1, while replace/remove/move-from rejected them, so under `validate: true` validate() and applyOperation() disagreed about the same path depending on the operation: applyOperation([10,20,30], {op:'add', path:'/01', value:9}, true) // mutated at index 1 applyOperation([10,20,30], {op:'replace', path:'/01', value:9}, true) // threw Reject a leading zero in isInteger so `/01` consistently raises OPERATION_PATH_ILLEGAL_ARRAY_INDEX, matching that error's own definition ("an unsigned base-10 integer value"). "0" and ordinary indices are unaffected. --- commonjs/helpers.js | 5 +++++ module/helpers.mjs | 5 +++++ src/helpers.ts | 5 +++++ test/spec/validateSpec.mjs | 8 ++++++++ 4 files changed, 23 insertions(+) diff --git a/commonjs/helpers.js b/commonjs/helpers.js index 5f2350e4..84b06e1c 100644 --- a/commonjs/helpers.js +++ b/commonjs/helpers.js @@ -62,6 +62,11 @@ function _deepClone(obj) { exports._deepClone = _deepClone; //3x faster than cached /^\d+$/.test(str) function isInteger(str) { + // A leading zero is not a valid array index per RFC 6901 section 4 + // (an index is "0", or a digit 1-9 followed by digits). + if (str.length > 1 && str.charCodeAt(0) === 48) { + return false; + } var i = 0; var len = str.length; var charCode; diff --git a/module/helpers.mjs b/module/helpers.mjs index 592f5605..5fc5c469 100644 --- a/module/helpers.mjs +++ b/module/helpers.mjs @@ -58,6 +58,11 @@ export function _deepClone(obj) { } //3x faster than cached /^\d+$/.test(str) export function isInteger(str) { + // A leading zero is not a valid array index per RFC 6901 section 4 + // (an index is "0", or a digit 1-9 followed by digits). + if (str.length > 1 && str.charCodeAt(0) === 48) { + return false; + } var i = 0; var len = str.length; var charCode; diff --git a/src/helpers.ts b/src/helpers.ts index 8a146290..590c5b31 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -45,6 +45,11 @@ export function _deepClone(obj) { } //3x faster than cached /^\d+$/.test(str) export function isInteger(str: string): boolean { + // A leading zero is not a valid array index per RFC 6901 section 4 + // (an index is "0", or a digit 1-9 followed by digits). + if (str.length > 1 && str.charCodeAt(0) === 48) { + return false; + } let i = 0; const len = str.length; let charCode; diff --git a/test/spec/validateSpec.mjs b/test/spec/validateSpec.mjs index 36941a7d..4ee48ac8 100644 --- a/test/spec/validateSpec.mjs +++ b/test/spec/validateSpec.mjs @@ -150,6 +150,14 @@ describe('validate', function() { expect(error.name).toBe('OPERATION_NOT_AN_OBJECT'); }); + it('should return an error for an array index with a leading zero', function() { + const error = jsonpatch.validate([ + { op: 'add', path: '/01', value: 9 } + ], [10, 20, 30]); + expect(error instanceof jsonpatch.JsonPatchError).toBe(true); + expect(error.name).toBe('OPERATION_PATH_ILLEGAL_ARRAY_INDEX'); + }); + it('should return an error if the operation "op" property is not a string', function() { const error = jsonpatch.validate([ {