Release JavaScript SDK v17.6.0 - #1111
Draft
stas-schaller wants to merge 21 commits into
Draft
Conversation
…stead of throwing
#1079) * chore(javascript): humanize comments and test names on the release branch Ticket refs belong in commit messages, not code; test names now describe the behavior under test instead of the ticket that prompted it. No behavior change.
…flat response array (#1076) Records created via non-SDK clients inside shared folders arrive in the flat response.records[] with innerFolderUid set, but recordKey is wrapped with the folder key, not the app key. The unconditional KEY_APP_KEY unwrap caused these records to be silently skipped. Mirrors the folderKeyMap pattern already shipped in the Java SDK (KSM-753).
…ap (#1077) * fix(javascript): KSM-1035 one-sided throttle jitter and retry_after cap throttleJitter previously returned [-0.25, 0.25), so a retry could fire before the computed backoff floor and immediately re-trigger the same throttle window. Narrowed to [0, 0.25), one-sided like the already-shipped Ruby fix (KSM-883). Also caps a server-supplied retry_after at MAX_THROTTLE_DELAY_SEC (176s), the same ceiling the exponential branch already reaches on its last retry. * fix(javascript): address KSM-1035 review feedback - Fix stale JSDoc on throttleDelay: jitter range is [0, 0.25) not [-0.25, 0.25) - Update jitter-bounds unit test to reflect one-sided range (floor is 11s not 8.25s) - Add 17.6.0 CHANGELOG entry for KSM-1035
All three are dev-only in this package (ts-jest transitive chain), never shipped: - minimatch -> 9.0.9 (CVE-2026-27903, CVE-2026-27904 ReDoS) - @babel/core -> 7.29.7 (CVE-2026-49356, arbitrary file read via sourceMappingURL) - handlebars -> 4.7.9 (CVE-2026-33938, CVE-2026-33941) Lockfile-only, no package.json range changes. Cherry-picked and scoped to sdk/javascript/packages/core/package-lock.json from 831b7b48, efde007d, and b9aef2fa (Sergey Aldoukhov). KSM-1217
…ery (#1078) * fix(javascript): KSM-1128 bound server-key-rotation retries in postQuery postQuery's error === 'key' branch (default, no custom server key pinned) saved the server's suggested key_id and retried with no iteration cap. A server that keeps rejecting the suggested key would retry forever. Adds a keyRotationAttempt counter bounded by MAX_KEY_ROTATION_RETRIES (3), mirroring the existing throttleAttempt/MAX_THROTTLE_RETRIES pattern in the same loop. * fix(javascript): address KSM-1128 review feedback - Import KeeperError for use at throw sites - Validate key_id before persisting: reject non-integer or non-positive values with KeeperError - Replace plain Error with KeeperError at all key-rotation throw sites - Tighten rotation-bound test: assert calls === 4 (MAX_KEY_ROTATION_RETRIES + 1), remove loose guard - Add happy-path test: single key rotation resolves on the retry * fix(javascript): KSM-1128 complete review feedback Blocking issue: Validate suggested key_id membership in keeperPublicKeys range. Unsupported key ids (outside 7-18) now throw typed KeeperError instead of silently persisting invalid config. Membership check is gated after custom-key check to preserve IL5 deployment support. Non-blocking improvements: - Diagnostic message now names the transmission key id actually attempted, not a future suggestion that was never sent. - Runaway loop guards in all key rotation tests prevent silent jest hang if retry bound breaks. - Better adoption verification: second test uses key_id 8 (not default 7) and asserts both transmission key and storage reflect the new id. - Helper functions (FAKE_ONE_TIME_TOKEN, keyErrorResponse) reduce duplicate literals and improve test maintainability. All 57 tests pass. * fix(javascript): KSM-1128 add membership check test and changelog Add regression test for unsupported key_id rejection to prevent silent config poisoning. Server response with unsupported key_id (e.g. 99) now correctly: - Makes exactly 1 network request (no retry loop) - Throws typed KeeperError with clear message - Does not persist the invalid id to storage Also add changelog entry documenting the key rotation bounding and validation improvements in v17.6.0. All 58 tests pass. * fix(javascript): KSM-1128 move shape guard below customKey branch When the server sends {"error":"key"} to a client with a pinned custom server public key (IL5 config), the IL5 diagnostic now always fires regardless of whether key_id is present or valid. Previously the shape guard above the customKey check intercepted malformed key_id values and produced a generic error instead of the actionable IL5 message. * fix(javascript): KSM-1128 apply non-blocking cosmetic fixes - IL5 diagnostic falls back to transmissionKey.publicKeyId when storage has no serverPublicKeyId yet, so the message never reads "id null" - Membership error derives the supported range from keeperPublicKeys keys at runtime instead of the hardcoded literal "7-18", so the message stays accurate when a new key is added to the table * fix(javascript): KSM-1128 rename rotation tests to behavior-based names The three tests added for the retry bound and membership check were prefixed "IL5 dynamic key - ..." but they test generic postQuery rotation behavior, not IL5-specific code paths. Renamed to describe what each test asserts.
…1254) (#1135) * fix(javascript): Node platform hash() ignores tag parameter (KSM-1254) hash() hardcoded 'KEEPER_SECRETS_MANAGER_CLIENT_ID' instead of hashing with its tag parameter, unlike the browser implementation and the Platform contract. No behavior change for the SDK's only caller, which already passed that same string as the tag. * test(javascript): pin the client id digest and guard Node/browser hash parity The two tests already on this branch prove hash() honors its tag, but nothing holds the result to a value computed outside the SDK, and nothing holds the two platform implementations to each other. Both gaps are what let the Node implementation hardcode its tag unnoticed since the initial commit: TypeScript accepts a lower-arity function for a higher-arity signature, so the compiler never objected, and every call site inside the SDK passes the one tag the buggy code hardcoded, so no integration-level test could tell the two apart. Add a separate file so the stacked KSM-1209 work, which appends to nodePlatform.test.ts, rebases without a conflict. - Pin the client id digest for a fixed key against a value cross-checked with the Python SDK's hmac.new(client_key_bytes, CLIENT_ID_HASH_TAG, 'sha512'), rather than recomputing it with the same call the implementation makes. - Assert the Node and browser implementations agree on a tag that appears nowhere in the SDK, which is the only input that separates an implementation honoring its tag from one hardcoding CLIENT_ID_HASH_TAG. Verified against the pre-fix implementation: the parity test fails, and the pinned digest still passes, which is the evidence for this branch's claim that the fix changes no behavior for existing callers. --------- Co-authored-by: Mateo Gallego <mgallego@keepersecurity.com>
test.js.yml triggered only on pull_request into master, so every JS core fix that goes to a release branch first, which is all of them, merged with no test signal. The only checks on those PRs are the Socket Security scans, and a green tick there says nothing about whether the SDK still passes. Add release/sdk/javascript/core/** to the pull_request branch filter. The workflow already lists itself under paths, so this change gates itself. Deliberately not adding a push trigger on release/**: that is the duplicate-run pattern KSM-1302 tracks in test.ruby.yml and the four JavaScript KMS workflows, where the pull_request run has already tested the same commit. Follows PR #1119, which made the same change to test.java.yml for KSM-1269. Both are legs of KSM-1284, which tracks the remaining SDK core workflows.
…ailure (#1141) Every IndexedDB call in the browser config storage wired only onsuccess, plus onupgradeneeded on the two open calls. A failing IDBRequest fires onerror and never onsuccess, so any storage failure left the returned promise pending forever: the caller waited with no error, no rejection and no timeout. Four of the eight wrappers even destructured reject from the Promise executor and never called it. secureStorage is the worst case. It awaits a read before it returns the storage object, so a failure hung the constructor itself and never handed the caller anything to catch. - Wire onerror on all eight requests, rejecting with a typed KeeperError that carries the underlying DOMException name and message. - Wire onblocked on both indexedDB.open calls. It fires when another live connection holds the database during a version change, and neither onsuccess nor onerror follows it. - Catch the synchronous throw from transaction() when the object store is missing. A throw inside an event handler is not caught by the enclosing Promise executor, so that path left the promise pending too. The cause is duck-typed on name and message rather than tested with instanceof Error: a DOMException only satisfies instanceof Error in the realm it was constructed in, so a cross-realm failure would otherwise lose the diagnostic. Adds test/browserConfigStorage.test.ts, covering both exported storages with a minimal IndexedDB double and no new dependency. Each failure test races a short timer so a regression fails fast with "promise never settled" instead of stalling the suite. Verified against the unfixed file: all eight failure tests fail there, and both happy-path tests pass on either side.
…stic
_isReadableJson only checked whether the base64-decoded text started with
'{' or '['. Genuine ciphertext has a ~1-in-128 chance of coincidentally
decoding to a leading '{' or '[' byte, which made hasEncryptedData() (and
hasReadableData()) misclassify real encrypted data as not encrypted.
_isReadableJson now requires the text to actually parse as valid JSON, not
just start with the right character, closing the gap consistently across
hasReadableData, hasEncryptedData, and getLinkData.
…-heuristic-176 KSM-1351: JavaScript SDK: fix hasEncryptedData misreading ciphertext as JSON
…te (KSM-1263) (#1131) fs.openSync's mode argument only takes effect when the file is created, so a config file that already existed with looser permissions kept them. Permissions are now explicitly reset to 0600 after every write.
…1152) * fix(javascript): KSM-1267 classify and surface getFolders() decryption failures getFolders() now classifies why an undecryptable folder was skipped (integrity, format, missing-key, malformed-data, or unknown) instead of logging an opaque, unclassified error, and logs one summary line naming every folder UID it had to omit. Adds an optional onDecryptionError callback to SecretManagerOptions, invoked once per skipped folder, so a caller can react to or throw to fail closed on a partial result; existing callers that do not set it see no behavior change. Both the Node and browser platforms' unwrap() now reject an unwrapped key of the wrong length immediately instead of caching it and failing later at an unrelated call site. The underlying finding (the shared-folder key wrap uses unauthenticated AES-256-CBC, a format fixed server-side that the SDK cannot change unilaterally) was reviewed and confirmed low-impact: a manipulated folder key is still caught by the existing AES-GCM authentication on the record keys inside that folder. * fix(javascript): KSM-1267 remove nonexistent getFolders2 reference in comment onDecryptionError's doc comment on SecretManagerOptions referenced getFolders2, which does not exist (unlike getSecrets/getSecrets2, getFolders has no "2" counterpart).
…amples (#1149) hello-secret and proxy-support set NODE_TLS_REJECT_UNAUTHORIZED='0' process-wide as their first line, with no self-signed-cert scenario in either example to justify it. Neither example needs the bypass; delete it rather than swap in a scoped alternative. KSM-1316
…inked-records examples (#1151) Five example areas the 2026-08-25 examples audit found genuinely missing from examples/javascript - no existing directory had any partial coverage of any of them. Each new directory mirrors hello-secret's existing style (plain CommonJS, Node-only, no build tooling) with its own README, since none of the three undocumented existing directories had one either. - notation: getNotationResults/tryGetNotationResults, throwing vs. non-throwing lookup against a missing field - folders: getFolders/createFolder/updateFolder/deleteFolder, with a README note on createOptions.folderUid needing a shared-folder UID - file-upload: uploadFile paired with downloadFile for a round-trip byte-compare, complementing hello-secret's existing download-only coverage - totp: getTotpCode against a record's oneTimeCode field, including the unixTimeSeconds override for deterministic use - pam-linked-records: getLinks()/KeeperRecordLink, the SDK's typed accessor for a PAM resource's linked credential/metadata/JIT/AI records via record.links - confirmed against the SDK's own record_link.test.ts rather than the recordRef-based mechanism the ticket description assumed, which doesn't exist as described All five verified: destructured import names checked against the published SDK, hello.js files pass node --check. KSM-1328
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
#1153) * fix(javascript): stop getSharedFolderUid from hanging on a folder parent cycle (KSM-1297) getSharedFolderUid walked the folder parent chain in an unbounded while loop with no visited set, so a cycle in server-supplied folder data (two folders naming each other as parent, or a folder naming itself) spun forever. Because the loop is synchronous with no await, this blocked the entire JS event loop in Node and froze the tab in a browser, not just the calling promise. Bound the walk with a visited set and throw a descriptive error naming the folder UID where the cycle closes. The existing per-folder try/catch in fetchAndDecryptFolders (added by KSM-1079) already skips and continues on any thrown error, so no caller changes are needed. Add regression coverage for both the Node and browser platform builds: self-cycle, two- and three-folder rings with exact log-message assertions, a 500-folder ring proving the fix is genuinely bounded rather than merely fast, a real two-level non-cyclic nested folder chain to confirm unchanged behavior, and the pre-existing "parent not found" case to confirm its message stays distinct from the new cycle message. * test(javascript): fix KSM-1297 cycle tests after KSM-1267's error classification landed KSM-1267 merged into this branch's base after KSM-1297 was opened. It added an upfront missing-key check in fetchAndDecryptFolders that now runs before getSharedFolderUid, plus a summary console.error line whenever any folder is skipped. The cycle test fixtures used an empty folderKey placeholder, which the new check now intercepts before the folder ever reaches the cycle-detection code, and the new summary line shifted every exact call-count assertion by one. Give the cyclic fixtures a non-empty placeholder folderKey so they still reach and exercise the actual cycle-detection path instead of short-circuiting on the new check, update call counts to account for the summary line, and update the exact log-message assertions to match the new classification-tagged format. The getSharedFolderUid fix itself is unchanged.
…nt to Vite (#1156) hello-secret (16.0.12), proxy-support and custom-caching-function-support (both 17.3.0) were stale relative to the SDK's current 17.6.0. hello-secret's yarn.lock is dropped rather than regenerated against 17.6.0, which hasn't published to npm yet, matching the other three example directories, none of which commit a lockfile. share-client also moves off Create React App (react-scripts 4.0.3, archived upstream) onto Vite, bumping react-scripts/react 17/typescript 4.1 to vite 8/react 19/typescript 5.9 and dropping the stale Node-12-era @types/node pin. README.md's dead IndexedDB blog link is removed as part of the same cleanup. Supersedes #1150, which merged into the wrong base branch and never reached this release branch. KSM-1320
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.
Summary
Release branch for JavaScript SDK v17.6.0: NSF folder-decryption parity, throttle hardening, per-item delete error surfacing, and key-rotation retry cap.
Changes
New Features
dbConnectionMethod(KSM-1073): addeddbConnectionMethodtoPamSettingsConnectionBug Fixes
getFolders()crash safety (KSM-1079): undecryptable folders are now skipped instead of throwing; the remaining folders are returned normallydeleteSecret()/deleteFolder()partial failure (KSM-1084): the SDK now surfaces per-item error messages from the server to the callerinnerFolderUidin the flatrecords[]array now use the folder key instead of the app key; this matches the behavior for records infolders[].records[]retry_afteris capped at 176spostQuery's{"error":"key"}branch now retries at most 3 times before throwing a typedKeeperError. The server-suggestedkey_idis validated for shape (positive integer) and membership in the bundled key table before being persisted; an unsupported id can no longer corrupt the stored configuration. PR fix(javascript): KSM-1128 bound server-key-rotation retries in postQuery #1078 merged 2026-08-18.Maintenance
minimatch,@babel/core, andhandlebarsdev dependenciesBreaking Changes
None.
Related Issues