Repository navigation
Fix: Reject out-of-range keys in JavaScript bindings - #812
Merged
ashvardanian merged 1 commit intoOct 24, 2026
Merged
Conversation
`normalizeKeys` only range-checked keys passed inside an array. A single `number` or any `bigint` went straight into `BigUint64Array.of(...)`, which wraps modulo 2^64. As a result `index.add(-1, ...)` stored the vector under 2^64 - 1, `index.add(2n ** 64n, ...)` stored it under 0, and `index.remove(-1)` deleted whatever was stored under 2^64 - 1. Check that every key, whether scalar or inside an array, is an integer within [0, 2^64) and throw otherwise, like the array path already did for negative numbers. Non-integer numbers now throw the same error instead of a bare `RangeError` from `BigInt()`.
ashvardanian
force-pushed
the
fix/js-validate-keys
branch
from
October 24, 2026 21:30
7f0f949 to
2d8766b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
normalizeKeysonly range-checks keys that are passed inside an array (number >= 0). A singlenumberand everybigintgo straight toBigUint64Array.of(BigInt(keys)), which silently wraps modulo 2^64:It also affects
remove: in the publishedusearch@2.26.5, with the key2n ** 64n - 1nin the index,idx.remove(-1)returns1and deletes that unrelated vector. Meanwhile the array formidx.add([-1], ...)already throwsAll keys must be positive integers or bigints., so the scalar and array paths disagree.This is input validation: invalid keys are rejected the same way on every path instead of being stored under a different key.
Change
normalizeKeyhelper used by the scalar and the array path. A key must be abigintor an integernumberand must be within[0, 2^64)(BigInt.asUintN(64, key) === key).All keys must be positive integers or bigints.on both paths (the scalar path used to leak a bareRangeErrorfromBigInt(1.5)). Out-of-range keys throwAll keys must be in the range [0, 2^64).NaNstill reports the existingKeys must be a number, bigint, ...error.contains,countandremovego through the samenormalizeKeys, so they now throw for such keys instead of looking up / removing the wrapped key.BigUint64Arrayinput is untouched.0n,2n ** 64n - 1nand plainnumberkeys likeindex.contains(15).Before / after
Published
usearch@2.26.5prebuilt binary, Windows x64, Node 24, fresh index with 2 vectors:idx.add(-1, v), thenidx.contains(2n**64n-1n)size3,trueError: All keys must be in the range [0, 2^64).idx.add(-1n, v)2^64 - 1idx.add(2n**64n, v), thenidx.contains(0n)size3,trueidx.add([2n**64n], v)idx.add([-1], v)All keys must be positive integers or bigints.All keys must be in the range [0, 2^64).idx.contains(-1)/idx.contains(2n**64n)false(wrapped lookup)idx.add(1.5, v)/idx.contains(1.5)RangeError: The number 1.5 cannot be converted to a BigInt ...Error: All keys must be positive integers or bigints.idx.add(2n**64n - 1n, v)Tests and what I could (not) run
New test
Keys outside of the unsigned 64-bit rangeinjavascript/usearch.test.js: for-1,-1n,-(2n**63n),2n**64n,2n**64n + 1nand1e20it checks thatadd(scalar and in an array) throws and leaves the index empty, thatcontains/count/removethrow and do not remove existing keys, and that the boundaries0n,2n**64n - 1nandNumber.MAX_SAFE_INTEGERare still accepted.I have no C++ toolchain, so I compiled
javascript/usearch.tswithtsc(CJS config) and ranjavascript/usearch.test.jsagainst theprebuilds/from the publishedusearch@2.26.5npm package (Windows x64 only):viewtests).javascript/usearch.tsreverted tomain-dev, new test kept: 18 failures, all in the new test (the bad keys are accepted instead of rejected).main-dev: 31 tests, 28 pass, 3 skipped.Not tested: Linux / macOS / arm64 binaries, building the addon from source, the ESM output.
Behaviour note:
contains(-1)and friends used to returnfalse(or, worse, match a wrapped key); they now throw, consistent withcontains([-1])already doing so.