Skip to content

Ruby SDK v17.2.1 - #1108

Merged
stas-schaller merged 4 commits into
masterfrom
release/sdk/ruby/v17.2.1
Sep 15, 2026
Merged

stas-schaller merged 4 commits into
masterfrom
release/sdk/ruby/v17.2.1

Conversation

@stas-schaller

@stas-schaller stas-schaller commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Ruby SDK v17.2.1: a single security fix in `create_secret`. This is a patch release and carries nothing else.

Changes

Security Fixes

  • Record key encryption on create (KSM-1193): `create_secret` now correctly encrypts the record key to the application owner's public key before including it in the create payload, matching the behavior of all other SDKs.

Maintenance

  • Version set to 17.2.1 in `version.rb`, `CHANGELOG.md`, and the README change log.

Security Impact

`create_secret` was omitting the record key wrap step. All records created via the Ruby SDK through v17.2.0 are affected. Upgrade to 17.2.1 to protect newly created records. Existing records are not remediable through the SDK.

Breaking Changes

`create_secret` now raises when the application config carries no `appOwnerPublicKey`, where it previously continued silently. This brings Ruby in line with all other SDKs.

Related Issues

…1107)

prepare_create_payload assigned the raw AES-256 record key to payload.record_key
with no encryption, while the same request carried the record data encrypted
under that key. The server therefore received both a record's ciphertext and the
key that opens it, which breaks the zero-knowledge property for every record the
Ruby SDK creates. Present since 17.1.0, so 17.1.0 and 17.2.0 are both published
with it.

The fix reads appOwnerPublicKey from storage, fails if it is absent, and wraps
the record key with Crypto.encrypt_ec, mirroring Python prepare_create_payload.
Ruby's upload_file path already did this correctly, so only the create-record
payload was affected. The hard failure is deliberate: a fallback to sending the
key unwrapped would leave the hole open for exactly the older applications most
likely to lack the key.

Three regression tests assert the property on the payload bytes rather than on
the code path. The second one is the defect stated as an assertion: it first
confirms the record key really does decrypt payload.data, then requires that the
value on the wire is not those bytes. The third forbids the silent-fallback fix.
Before the fix all three fail, and the second reports expected and actual as
byte-identical 32-byte strings.

Five existing create_secret specs shared a config fixture with no owner public
key, which is why the defect shipped twice with a green suite. The fixture now
carries a real P-256 key. No assertion in those specs changed.

Verified: 783 examples, 0 failures. Live check against QA created a record,
confirmed payload.recordKey is 125 bytes beginning with 0x04 rather than the
32-byte plaintext key, read the record back with its field values intact, and
deleted it.
The entry named 17.1.0 and 17.2.0. All four published versions carry the
defect, including 17.0.3 and 17.0.4.

Verified by downloading each gem from RubyGems and reading
prepare_create_payload: the unwrapped assignment is present in every one,
byte-identical. It is also present in the SDK's first commit, d026001, so it
was never correct rather than introduced by a later change. The git history
misled the original entry because the Ruby SDK's history in this repository
begins at the v17.1.0 release, four months after 17.0.3 had already published.

This matters to a reader rather than only to the record: naming two versions
implicitly cleared the other two, so a customer on 17.0.4 would have taken a
security note as an all-clear.
craiglurey
craiglurey previously approved these changes Aug 13, 2026

@mgallego-keeper mgallego-keeper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. I verified the fix independently end to end; details below. The asks in the inline comments are non-blocking, but the docs ones only count if they land before the gem publish dispatch, since README, CHANGELOG, and gemspec metadata are baked into the 17.2.1 package.

Verification performed

  • Full suite locally on Ruby 3.3.8: 783 examples, 0 failures, 13 pre-existing pending; the KEEPER_MOCK_MODE offline integration test is green.
  • All three new specs fail on the unfixed code for the advertised reasons (raw key on the wire, EC point parse failure on decrypt, no raise). core_spec.rb alone passes 77/77 in both fixed and unfixed states, which confirms the description's explanation of why the old suite never caught this.
  • Cross-language wire check: bytes from Ruby Crypto.encrypt_ec decrypt with the Python SDK's decrypt_ec recipe; identical ECIES construction (ephemeral P-256, ECDH x-coordinate, SHA-256 KDF, AES-256-GCM), 125 bytes with leading 0x04 for a 32-byte key, matching the live QA observation in the description.
  • Owner-key decode verified against all four stored formats (url-safe or standard alphabet, padded or unpadded), including keys whose base64 contains + or /, so Commander-written configs, Web Vault configs, and the SDK's own binding format all work.
  • All six other SDKs hard-fail without appOwnerPublicKey as claimed; the new message is byte-identical to Python's current one.
  • client_version derives from VERSION, so fixed clients stamp mb17.2.1, cleanly separable from mr17.0.0 / mr17.91.0 / mb17.2.0 for server-side forensics.
  • The branch carries exactly these six files over master (ahead 3, behind 0), and the affected-version list matches RubyGems (17.0.0-17.0.2 were never published).
  • Payload sweep found no sibling issue: every other key the SDK transmits is wrapped (folderKey, file keys, shared-folder traversal), and UpdatePayload has no key field, confirming the cannot-rotate statement.

One ask that cannot be an inline comment (file not in the diff): keeper_secrets_manager.gemspec:18 sets changelog_uri to a repo-root CHANGELOG.md that does not exist, so the Changelog link on the 17.2.1 rubygems.org page will 404 at exactly the moment users come looking for this advisory. One-line change to .../blob/master/sdk/ruby/CHANGELOG.md; worth including in this release since the metadata ships in the gem.

PR description nit: "Breaking Changes: None for any configuration that can create records today" contradicts the sentence that follows it; a configuration without the owner key can create records today (that is the vulnerability) and will raise after upgrading. "Any correctly provisioned configuration" is probably the intended meaning.

Optional, while both docs files are open: the neighboring dates look wrong: 17.1.0 is dated 2025-01-06 in both files while the gem published on 2025-11-06 (looks like a 01/11 transposition), and the README's 17.2.0 entry says 2025-11-14 while the gem published on 2026-07-20. If 17.2.1 gets a date, best not to propagate the pattern.

Pre-existing items found during the review are filed separately so nothing extra lands on this security patch: KSM-1223 (undefined KEY_ENCRYPTED_APP_KEY raises NameError on the no-appKey path), KSM-1224 (get_file_data output incompatible with download_file), KSM-1225 (redact key material from debug logging), KSM-1226 (owner-key handling unification and payload-builder hardening), KSM-1227 (remove the silent AES-GCM to CBC fallbacks), KSM-1228 (0600 on ksm_cache.bin). The application-log aspect of the exposure is noted on KSM-1193.

Comment thread sdk/ruby/CHANGELOG.md Outdated
Comment thread sdk/ruby/CHANGELOG.md Outdated
Comment thread sdk/ruby/README.md Outdated
Comment thread sdk/ruby/lib/keeper_secrets_manager/core.rb
@stas-schaller

stas-schaller commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thank ya, here are my additions:

  • keeper_secrets_manager.gemspec:18 — changelog_uri now points at sdk/ruby/CHANGELOG.md (the blocking one ahead of gem publish)
  • CHANGELOG: KSM-1193 entry moved under ### Security, added the 17.0.0-17.0.2-never-published clause, scoped "existing records" to "records created by an earlier version," added the missing [17.2.1] compare link
  • README: documented the create_secret raise on missing appOwnerPublicKey and the remedy (re-bind, or KSM_APPOWNERPUBLICKEY for EnvironmentStorage)
  • PR description: "None for any configuration that can create records today" → "None for any correctly provisioned configuration"
  • Dates: went further than the transposition fix and dropped dates from every version header in both files (CHANGELOG.md and the README change log) — publish date isn't known when the branch is prepared, so we don't carry it on the header at all going forward

@mgallego-keeper mgallego-keeper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 8525296; everything verified on the new head.

  • CHANGELOG: entry now under ### Security (matching this file's own 17.0.2 precedent), [17.2.1] compare link defined, and both wording fixes are in (never-published clause, scoped re-creation sentence).
  • README: documents the new raise and the actual remedy (re-bind with a fresh one-time token, or KSM_APPOWNERPUBLICKEY on the read-only EnvironmentStorage).
  • Gemspec: changelog_uri fixed. I built the gem from this head and the packaged metadata carries the corrected link (keeper_secrets_manager-17.2.1.gem -> blob/master/sdk/ruby/CHANGELOG.md).
  • Dates: removing them from all version headers instead of adding one is a sensible resolution; the provably wrong dates are gone and rubygems.org remains the single source of truth for publish timestamps.
  • Deferrals (guard error contract, spec strengthenings) match what KSM-1226 already tracks; agreed they do not belong on this security patch.
  • Re-verified locally on 8525296: 783 examples, 0 failures, 13 pre-existing pending; offline mock test green; gem build clean at 17.2.1.

Resolving the five threads. Still approved; good to publish and merge.

- move KSM-1193 CHANGELOG entry under ### Security (matches file's own
  precedent for security items)
- clarify 17.0.0-17.0.2 were never published to RubyGems
- scope "existing records" wording to records created by an earlier version
- add missing [17.2.1] compare link
- drop dates from all version headers in CHANGELOG.md/README.md
- README: document create_secret's new raise and the re-bind/env-var remedy
- gemspec: fix changelog_uri to point at sdk/ruby/CHANGELOG.md, not a
  nonexistent repo-root file

@mgallego-keeper mgallego-keeper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved at dd78c5a. The branch was force-pushed over my previous approval with one amendment: two lines of advisory wording in CHANGELOG.md and README.md ("placed the record key in the create payload unencrypted" becomes "without that wrap"), which is the more precise phrasing given the encrypted transport envelope. Verified the full tree delta from the previously approved 8525296 is exactly those two lines and nothing else, and that the published gem (17.2.1, sha256 ca73f928d6d51e421394ecfb1ca03181e5a3a4c8823157a906494b0b0206a7a7) is byte-identical to this head: lib/ and the gemspec match the reviewed code, the docs match the amendment. Cumulative diff vs master remains the same seven files. Still approved.

@stas-schaller
stas-schaller merged commit d02e59f into master Sep 15, 2026
22 checks passed

This branch was previously deployed

1 inactive deployment
prod — dd78c5ad Deployed Aug 13, 2026 by stas-schaller via Publish Ruby SDK to RubyGems #18
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