Release/sdk/ruby/v17.2.0#869
Open
stas-schaller wants to merge 41 commits into
Open
Conversation
174e5ab to
ba2ebd8
Compare
ba2ebd8 to
e9fd42b
Compare
ff93750 to
17aef6a
Compare
… for API responses
… languages Added 8 missing DTO fields to achieve complete parity with Python, JavaScript, .NET, and Java SDKs: - KeeperRecord.links - Array of linked record references - KeeperRecord.is_editable - Boolean indicating edit permissions (defaults to true) - KeeperRecord.inner_folder_uid - Actual folder UID where record is located - KeeperFile.thumbnail_url - Thumbnail image URL - KeeperFile.last_modified - File modification timestamp - QueryOptions.request_links - Flag to request linked records in query - UpdatePayload.links2_remove - Array of link UIDs to remove during updates - SecretsManagerResponse.expires_on - Token expiration timestamp Added UpdateOptions class with transaction_type and links_to_remove support. Added/updated methods: - update_secret_with_options(record, update_options) - New method for advanced updates - update_secret(record, transaction_type:) - Refactored to use update_secret_with_options - prepare_update_payload - Now accepts UpdateOptions, filters fileRef fields - prepare_get_payload - Now supports request_links parameter - download_thumbnail(file_data) - New method for downloading file thumbnails
…HANGELOG cleanup, test coverage expansion, version bump to 17.2.0
…workflows and update examples with from_file() initialization
…ueryOptions, PAM GraphSync, and disaster recovery caching; fix core_spec token and AES-GCM mocks; update CHANGELOG for 17.2.0 and fix config file permissions
A GCM authentication-tag failure raised OpenSSL::Cipher::CipherError, which was caught and silently retried as AES-CBC. If the CBC attempt succeeded, tampered or wrong-key ciphertext could produce output with no error. Now propagates as DecryptionError immediately.
…ded jitter Co-authored-by: Stas Schaller <[email protected]>
…t and delete_folder
…SM-1090) Ruby 4.0 unbundled base64 and logger from the default standard library. Both are maintained by the Ruby core team and were previously available as implicit default gems. This makes the dependency explicit so any clean install on Ruby 4.0+ resolves them without a LoadError. Also removes a dead require 'ostruct' from dto.rb (OpenStruct is not used) and updates the Ruby version guard in core.rb to match the gemspec's required_ruby_version (>= 3.1.0 vs the stale 2.6 check).
Remove the !custom.empty? guard from KeeperRecord#to_h so that an empty custom array is serialized as "custom": [] in the V3 API payload, matching Commander and Vault behavior. The field was already initialized to [] in both constructor paths; only serialization was wrong.
Add 'IL5' => 'il5.keepersecurity.us' to the KEEPER_SERVERS hash. Token parsing already handles region lookup generically so no changes to core.rb are needed.
Add nil guards to url_safe_str_to_bytes and base64_to_bytes in both Utils and Crypto modules so passing nil raises an explicit Error rather than propagating to the Ruby standard library with a confusing TypeError. Fix the one Crypto.url_safe_str_to_bytes call in core.rb to route through Utils, making Utils the single canonical Base64 interface for core.rb.
…SM-1013) Typed linked-credential accessor layer for Ruby SDK. KeeperRecordLink wraps raw links entries with never-raising typed accessors: permission booleans with allowedSettings fallback, AES-256-GCM get_decrypted_data/get_link_data, and meta/ai_settings/jit_settings settings accessors. Adds request_links: keyword to get_secrets. Purely additive; raw record.links list is unchanged. Mirrors Python reference (KSM-992).
Previously, an invalid proxy_url was silently stored and only surfaced at request time — either as a silent proxy bypass (when URI.parse accepted the string but returned no host) or as a NetworkError (when URI raised InvalidURIError deep in the call stack). Now initialize validates the value immediately: raises ArgumentError for non-HTTP/HTTPS schemes, missing hosts, and unparseable strings.
`update_secret` referenced `record_uid` (defined only in `update_secret_with_options`) when refreshing the local revision after a successful update. Calling the method with a KeeperRecord always raised NameError. Changed to `record.uid`.
…#1060) `update_secret` staged the update via `update_secret_with_options` but never called `complete_transaction`, leaving changes in a staged state that the server never committed. Added a `complete_transaction(uid)` call after the update succeeds, covering both KeeperRecord objects and plain hash inputs.
…1061) `download_thumbnail` called `file_data['fileUid']` without checking whether `file_data` supports `[]`, causing NoMethodError when a KeeperFile object was passed. Added an `is_a?(KeeperFile)` branch that reads via accessors. Also added `file_key` attr_accessor to KeeperFile so the decrypted key is accessible from the object.
Update two test cases that still reflected the old symmetric ±25% jitter behavior after the implementation was changed to one-sided (0..+25%): - Rename and repin the jitter bounds test: replace the impossible rand→-0.25 pin (floor 8.25) with rand→0.0 (floor 11.0), so the test can actually catch a regression back to symmetric jitter. - Fix the retry_after e2e assertion: lower bound was 2.25 (3×0.75), which one-sided jitter can never produce; corrected to 3.0 with comment updated to reflect [3.0s, 3.75s] range.
f13418d to
831f264
Compare
try_get_notation returned a bare scalar on success but [] on error, so the .first || default pattern shown in the ticket's own examples raised NoMethodError once a real notation resolved.
…notation
parse_notation raises NoMethodError when called with nil because it calls
notation.include?('/') without a nil check. This path was reachable via
get_value (and its wrappers get_totp_code/download_file) in
notation_enhancements.rb, which called parse_notation directly after the
auto_process gate without guarding for bad input.
Also fixes parse itself: the empty? check fired before is_a?(String),
so parse(42) raised NoMethodError instead of NotationError.
notation_enhancements.rb is now required from the main entry point so
its public methods are accessible.
…OTT, keyId 20) * feat(ruby): KSM-906 add IL5 dynamic transmission key support (4-part OTT, keyId 20) Extend IL5 support beyond region mapping to redeem the 4-part one-time token IL5:clientKey:serverPublicKeyId:serverPublicKey. The supplied EC P-256 server public key (keyId 20, outside the built-in 1-18 table) is registered for ECIES transmission-key wrapping and persisted as serverPublicKey so it survives restarts. - process_token_binding: region-gated 4-part OTT parse with segment and base64 validation; non-IL5 tokens unchanged - bind_one_time_token: persist serverPublicKeyId + serverPublicKey - generate_transmission_key: prefer a configured custom key - initialize: add server_public_key / server_public_key_id options (precedence: programmatic > token > config) and a non-clobbering key-id fallback - handle_http_error: actionable error when a configured custom key is rejected - add ConfigKeys::KEY_SERVER_PUBLIC_KEY and KSM_CLIENT_VERSION override - add unit/edge spec (19 examples) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * Update config_keys.rb * Update core.rb * Update keeper_globals.rb --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Add build-only toggle to publish workflow (matching JS/Python pattern) so the SBOM scan can be validated without pushing to RubyGems. Update Syft to v1.32.0 and Manifest CLI to v0.30.0. Pin ksm-action to SHA. Update actions/checkout to v4. Install SBOM tools to /usr/local/bin to avoid per-step PATH exports. Add release/sdk/ruby/** branch triggers to the test workflow so PRs targeting release branches run tests automatically.
Replace long-lived API key (fetched via ksm-action) with rubygems/release-gem OIDC trusted publishing. Short-lived scoped token is exchanged at runtime; no credentials stored. RubyGems.org trusted publisher already configured for Keeper-Security/secrets-manager + publish.rubygems.ruby.sdk.yml + prod env.
Scan an isolated gem install target instead of the source tree so only runtime deps appear in the SBOM. Previously Gemfile.lock was scanned, pulling in dev deps (diff-lcs via RSpec) and unrelated Ruby stdlib gems (racc, io-console, bigdecimal, reline), producing false-positive license flags. Now: build the gem, install to /tmp/ruby-scan-target (runtime deps only), scan that. Resolves all 3 forbidden-license findings. Also: upgrade actions/checkout to v5.0.1 (node24) and actions/upload-artifact to v7.0.1 (node24) to clear Node.js 20 deprecation warnings; clamp retention-days to 10 (repo maximum).
Add missing KSM-696 (secure file permissions) to the Fixed section. Merge duplicate Fixed/Added section headers, normalize all ticket refs to bold **KSM-XXXX**: format, collapse multi-line Fixed entries to single lines, and move caching test count from Added to Changed.
Fixes found during example execution audit against production vault:
API misuse (nonexistent methods):
- get_secret_by_uid → get_secrets([uid]).first (03, 04, three occurrences)
- get_secrets([], query_options) → get_secrets_with_options(query_options) (03, 11)
- storage.to_base64 → Base64.strict_encode64(storage.to_json) (00, 02)
Wrong type assumptions:
- record.fields treated as Hash; it is an Array — fix .keys and ['login']
access to .map{|f| f['type']} and record.login/record.url (03)
TOTP:
- nil seed guard added before generate_code (09)
- otpauth:// URIs parsed via TOTP.parse_url(seed)['secret'] before
passing raw secret to generate_code (09)
Logic bugs:
- Re-fetch record between saves in section 3.7 to avoid stale-object
out-of-sync error on second save (04)
- Delete verification replaced rescue with nil check; get_secrets
returns nil for deleted records, does not raise (04)
- Folder-move via update_secret removed; update_secret does not
serialize folder_uid so cannot move records between folders;
replaced with create_secret_with_options into a folder (07)
Misleading snippet:
- Certificate upload example corrected from keyword args to positional
args matching upload_file(uid, data, name, title=nil) (06)
All fixes verified against production vault.
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 Ruby SDK v17.2.0: security hardening, throttle retry backoff, full PAM/DTO parity with other SDKs, and accumulated bug fixes since v17.1.0.
Changes
New Features
post_queryretries up to 5 times on HTTP 403{"error":"throttled"}with delays of 11s–176s plus 0–25% one-sided jitter. RaisesThrottledErroronce retries are exhausted.complete_transaction,UpdateOptions,update_secret_with_options,download_thumbnail, and matching fields onKeeperRecord/KeeperFileto reach parity with other SDKs.KeeperRecordLinkwraps each rawlinksentry with never-raising typed accessors — permission booleans withallowedSettingsfallback (top-level wins), AES-256-GCMget_decrypted_data/get_link_data, andmeta/ai_settings/jit_settingssettings accessors. Adds arequest_links:keyword toget_secrets. Purely additive; the rawrecord.linkslist is unchanged. Mirrors the Python reference (KSM-992).proxy_urlparam,HTTPS_PROXY/https_proxyenv vars, authenticated proxy; applies to all HTTP operations.CachingPostFunctionandCachefor encrypted file-based caching of API responses; location viaKSM_CACHE_DIR.upload_file_from_pathandtry_get_notation(returns empty array instead of raising).from_configinitializer: Initialize from base64 config string, complementingfrom_tokenandfrom_file.create_secret_with_options(KSM-1099): Accepts pre-fetched folders to skip theget_foldersnetwork call when the caller already has them.create_secretis unchanged.get_notation_results/try_get_notation_results(KSM-1100): List-returning notation lookup that always returnsArray[String], returns all field values by default (no first-element shortcut), and JSON-serializes complex values.try_get_notation_resultsnever raises.inflate_field_value/get_inflate_ref_types(KSM-1101): ResolvesaddressRefandcardReffield references to their underlying field data; supports recursive inflate (cardRef contains addressRef UIDs).save/save_with_options(KSM-1102): Non-finalizing update aliases that use the stored record key without re-fetching and do not callcomplete_transaction. Usesave(record, transaction_type: 'rotation')to stage a PAM rotation and finalize separately.Bug Fixes
customfield (KSM-824):to_hnow always includescustom: []in the API payload even when empty, matching Commander and Vault behavior.IL5→il5.keepersecurity.ustoKEEPER_SERVERS;IL5:-prefixed tokens now resolve to the correct hostname.url_safe_str_to_bytesandbase64_to_bytesinUtilsraiseErroron nil input instead of propagating to the standard library. All Base64 decoding incore.rbnow routes throughUtils.decrypt_aes_gcmno longer silently retries failed AES-GCM decryption as AES-CBC. Authentication-tag failures now raiseDecryptionErrorimmediately.delete_secretanddelete_foldernow log an error per item whoseresponseCodeis not"ok"; previously silent on partial failures.subfolder_uidsent correctly (KSM-685):CreateOptions.subfolder_uidwas missing from the create payload.0600on Unix.proxy_urlvalidation (KSM-1091): Invalidproxy_urlnow raisesArgumentErrorat initialization with a descriptive message. Previously, a URL with no host silently bypassed the proxy and a malformed URL raisedNetworkErrorat request time.update_secretNameError (KSM-1094):update_secretno longer raisesNameError: undefined local variable 'record_uid'when called with aKeeperRecordobject; the revision refresh now correctly referencesrecord.uid.update_secretauto-finalize (KSM-1095):update_secretnow callscomplete_transactionafter staging the update so changes are committed to the server rather than remaining staged indefinitely; works for bothKeeperRecordobjects and plain hash inputs.download_thumbnailNoMethodError (KSM-1096):download_thumbnailno longer raisesNoMethodErrorwhen passed aKeeperFileobject.KeeperFilenow exposes afile_keyattribute and the method dispatches on type before attempting hash access.notation_enhancementsnil input (KSM-1098):get_value,get_totp_code, anddownload_fileno longer raiseNoMethodErrorwhen passednil, an empty string, or a non-String value; they returnnilimmediately. Also fixedparseitself, which calledempty?before theis_a?(String)check.notation_enhancementsis now properly required from the main entry point.Maintenance
base64andloggerdeclared as explicit runtime dependencies; both were removed from Ruby 4.0's default standard library.Breaking Changes
None.
Related Issues