Skip to content

Release/sdk/ruby/v17.2.0#869

Open
stas-schaller wants to merge 41 commits into
masterfrom
release/sdk/ruby/v17.2.0
Open

Release/sdk/ruby/v17.2.0#869
stas-schaller wants to merge 41 commits into
masterfrom
release/sdk/ruby/v17.2.0

Conversation

@stas-schaller

@stas-schaller stas-schaller commented Nov 17, 2025

Copy link
Copy Markdown
Contributor

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

  • Throttle retry with exponential backoff (KSM-883): post_query retries up to 5 times on HTTP 403 {"error":"throttled"} with delays of 11s–176s plus 0–25% one-sided jitter. Raises ThrottledError once retries are exhausted.
  • PAM transaction support and DTO parity (KSM-687): complete_transaction, UpdateOptions, update_secret_with_options, download_thumbnail, and matching fields on KeeperRecord/KeeperFile to reach parity with other SDKs.
  • Linked credential typed accessors (KSM-1013): KeeperRecordLink wraps each raw links entry with never-raising typed accessors — permission booleans with allowedSettings fallback (top-level wins), AES-256-GCM get_decrypted_data/get_link_data, and meta/ai_settings/jit_settings settings accessors. Adds a request_links: keyword to get_secrets. Purely additive; the raw record.links list is unchanged. Mirrors the Python reference (KSM-992).
  • HTTP proxy support (KSM-692): proxy_url param, HTTPS_PROXY/https_proxy env vars, authenticated proxy; applies to all HTTP operations.
  • Disaster recovery caching (KSM-686): CachingPostFunction and Cache for encrypted file-based caching of API responses; location via KSM_CACHE_DIR.
  • Convenience methods (KSM-694): upload_file_from_path and try_get_notation (returns empty array instead of raising).
  • Transmission key renamed module to keepercommandersm and published it to pypi #18 (KSM-743): Gov Cloud Dev environment support.
  • from_config initializer: Initialize from base64 config string, complementing from_token and from_file.
  • create_secret_with_options (KSM-1099): Accepts pre-fetched folders to skip the get_folders network call when the caller already has them. create_secret is unchanged.
  • get_notation_results / try_get_notation_results (KSM-1100): List-returning notation lookup that always returns Array[String], returns all field values by default (no first-element shortcut), and JSON-serializes complex values. try_get_notation_results never raises.
  • inflate_field_value / get_inflate_ref_types (KSM-1101): Resolves addressRef and cardRef field 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 call complete_transaction. Use save(record, transaction_type: 'rotation') to stage a PAM rotation and finalize separately.

Bug Fixes

  • V3 payload custom field (KSM-824): to_h now always includes custom: [] in the API payload even when empty, matching Commander and Vault behavior.
  • IL5 region mapping (KSM-906): Added IL5il5.keepersecurity.us to KEEPER_SERVERS; IL5:-prefixed tokens now resolve to the correct hostname.
  • Base64 nil guards (KSM-987): url_safe_str_to_bytes and base64_to_bytes in Utils raise Error on nil input instead of propagating to the standard library. All Base64 decoding in core.rb now routes through Utils.
  • GCM decryption fallback removed (KSM-1070): decrypt_aes_gcm no longer silently retries failed AES-GCM decryption as AES-CBC. Authentication-tag failures now raise DecryptionError immediately.
  • Per-item delete errors surfaced (KSM-1088): delete_secret and delete_folder now log an error per item whose responseCode is not "ok"; previously silent on partial failures.
  • subfolder_uid sent correctly (KSM-685): CreateOptions.subfolder_uid was missing from the create payload.
  • Notation with duplicate UIDs (KSM-734): No longer raises ambiguity errors when the same UID appears twice (original + shortcut).
  • Secure file permissions (KSM-696): Config files created with 0600 on Unix.
  • proxy_url validation (KSM-1091): Invalid proxy_url now raises ArgumentError at initialization with a descriptive message. Previously, a URL with no host silently bypassed the proxy and a malformed URL raised NetworkError at request time.
  • update_secret NameError (KSM-1094): update_secret no longer raises NameError: undefined local variable 'record_uid' when called with a KeeperRecord object; the revision refresh now correctly references record.uid.
  • update_secret auto-finalize (KSM-1095): update_secret now calls complete_transaction after staging the update so changes are committed to the server rather than remaining staged indefinitely; works for both KeeperRecord objects and plain hash inputs.
  • download_thumbnail NoMethodError (KSM-1096): download_thumbnail no longer raises NoMethodError when passed a KeeperFile object. KeeperFile now exposes a file_key attribute and the method dispatches on type before attempting hash access.
  • notation_enhancements nil input (KSM-1098): get_value, get_totp_code, and download_file no longer raise NoMethodError when passed nil, an empty string, or a non-String value; they return nil immediately. Also fixed parse itself, which called empty? before the is_a?(String) check. notation_enhancements is now properly required from the main entry point.

Maintenance

  • Ruby 4.0 compatibility (KSM-1090): base64 and logger declared as explicit runtime dependencies; both were removed from Ruby 4.0's default standard library.

Breaking Changes

None.

Related Issues

@stas-schaller stas-schaller marked this pull request as ready for review November 20, 2025 17:15
@stas-schaller stas-schaller force-pushed the release/sdk/ruby/v17.2.0 branch from 174e5ab to ba2ebd8 Compare March 27, 2026 18:32
@stas-schaller stas-schaller force-pushed the release/sdk/ruby/v17.2.0 branch from ba2ebd8 to e9fd42b Compare April 9, 2026 16:09
@stas-schaller stas-schaller force-pushed the release/sdk/ruby/v17.2.0 branch from ff93750 to 17aef6a Compare July 8, 2026 16:56
Comment thread sdk/ruby/lib/keeper_secrets_manager/core.rb Dismissed
stas-schaller and others added 25 commits July 13, 2026 16:07
… 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.
…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.
@stas-schaller stas-schaller force-pushed the release/sdk/ruby/v17.2.0 branch from f13418d to 831f264 Compare July 13, 2026 21:16
stas-schaller and others added 12 commits July 13, 2026 17:31
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants