Skip to content

Release Rust SDK v17.2.0#993

Open
stas-schaller wants to merge 41 commits intomasterfrom
release/sdk/rust/v17.2.0
Open

Release Rust SDK v17.2.0#993
stas-schaller wants to merge 41 commits intomasterfrom
release/sdk/rust/v17.2.0

Conversation

@stas-schaller
Copy link
Copy Markdown
Contributor

@stas-schaller stas-schaller commented Apr 13, 2026

Summary

Rust SDK v17.2.0 — bug fix release.

Bug Fixes

  • KSM-886: Fixed get_file_data() and get_thumbnail_data() failing with "builder error" when called from inside tokio::spawn_blocking. Root cause: reqwest::blocking::Client creates an internal tokio runtime on construction; building it inside an existing async context (e.g. KeeperDB Proxy in Docker) panics. Fix: build a single shared client in SecretsManager::new() and propagate it to KeeperFile objects. (#991)

  • KSM-812: Fixed get_folders() consuming the SecretsManager instance (self) instead of borrowing it (&mut self), forcing unnecessary clones. Now consistent with all other methods. Closes #950. (#992)

  • KSM-925: Removed three self.clone().get_folders() workarounds left over from the KSM-812 signature fix. Each clone triggered an unnecessary extra network round-trip; all three callsites now call self.get_folders() directly. (#1002)

  • KSM-926: post_function (every API call) and upload_file_function (multipart file upload) each built a new reqwest::blocking::Client per call, leaving the same nested-runtime panic risk under tokio::spawn_blocking that KSM-886 fixed for file downloads. Both callsites now reuse the shared client built in SecretsManager::new(). Also resolved a long-standing semantic inversion of verify_ssl_certs — the field was assigned and read with opposite conventions across two code paths. The field now uniformly means "do verify"; all danger_accept_invalid_certs calls route through a new skip_ssl_verify() accessor. Net behavior: insecure_skip_verify=true and KSM_SKIP_VERIFY=true now both correctly relax TLS on every HTTP path; the default is strict on every HTTP path. (#1003)

  • KSM-931: caching_post_function built a new reqwest::blocking::Client on every call, leaving the same nested-runtime panic under tokio::spawn_blocking that KSM-886/926 fixed for the standard paths. New caching::make_caching_post_function(client) factory captures a pre-built client in a closure and reuses it across calls. The bare caching_post_function is retained for synchronous callers with a doc warning. (#1004)

  • KSM-933: get_secrets() propagated SecretsManager.verify_ssl_certs (positive-sense: true = strict) directly into KeeperFile.skip_ssl_verify (negative-sense: true = skip), inverting TLS intent on every file attachment in strict mode. Both propagation sites now use self.skip_ssl_verify(), which correctly returns !verify_ssl_certs. Currently masked by the shared http_client path but would have become a silent security regression on any future refactor. (#1005)

  • KSM-934: The module-level doc example in caching.rs, the Disaster Recovery Caching example in README.md, and the Proxy section in README.md all still pointed users at caching_post_function after KSM-931 introduced make_caching_post_function as its safe replacement. All three updated to show make_caching_post_function(client) as the primary API; bare function retained with non-async caveat in the Proxy section. (#1006)

  • KSM-936: RecordField::new unconditionally wrapped the supplied Value in a single-element array, so callers passing a Value::Array (the documented way to create multi-value fields like phone and securityQuestion) produced [[obj1, obj2]] on the wire instead of [obj1, obj2]. The server stored the wrong shape, causing keeper://UID/field/phone[1] to return "idx out of range: 1" and records to appear single-valued to all other SDKs.

  • KSM-937: examples/manual_tests/06_caching_function.rs still used the deprecated bare caching::caching_post_function after KSM-934 updated the module doc and README. Users copying the example into a tokio app would intermittently hit the KSM-886 panic. Updated to build one reqwest::blocking::Client outside any async context and pass it to caching::make_caching_post_function(client).

Internal / Housekeeping

  • KeeperFile::http_client and skip_ssl_verify fields narrowed from pub to pub(crate) — implementation details with no external use case
  • client_builder.build().ok()build().map_err(...)? in SecretsManager::new() — TLS init failures now surface at construction time
  • Extracted KeeperFile::resolve_http_client() helper to eliminate duplicated client-building fallback
  • Fixed pre-existing compile error in empty_config_test.rs (missing proxy_url argument in ClientOptions::new() calls)

Breaking Changes

  • KSM-812: get_folders() signature changed from self to &mut self. Callers that relied on the consuming signature must remove any .clone() added to work around the original bug.
  • KSM-931: CustomPostFunction type alias changed from fn(...) to Arc<dyn Fn(...) + Send + Sync>. Code that stored the alias directly must wrap with Arc::new(...). Call sites using options.set_custom_post_function(my_fn) are unaffected — set_custom_post_function now accepts impl Fn(...) and wraps internally.

Related Issues

maksimu and others added 12 commits April 11, 2026 09:56
Building a new reqwest::blocking::Client inside tokio::spawn_blocking
fails with "builder error" because reqwest's blocking module creates an
internal tokio runtime that conflicts with the existing one.

This affected get_file_data() and get_thumbnail_data() in KeeperFile,
which built a fresh HTTP client per call (dtos.rs:1155). The main API
calls in post_query() worked because they configured
danger_accept_invalid_certs which changed the TLS init path.

Fix:
- Build one reqwest::blocking::Client in SecretsManager::new() after
  SSL/proxy config is resolved
- Store it on the SecretsManager struct, propagate to KeeperFile
  instances (same pattern as proxy_url propagation)
- get_file_data() and get_thumbnail_data() reuse the pre-built client
  when available, fall back to building a new one for backward compat
- Add skip_ssl_verify field to KeeperFile (propagated from
  SecretsManager.verify_ssl_certs) for the fallback path

Precedent: OpenTelemetry Rust (issue #2400), TiKV rust-prometheus
(PR #343), reqwest docs all recommend building the blocking client
outside async runtimes. See: seanmonstar/reqwest#1017
…client

fix(rust-sdk): KSM-886 reuse HTTP client for file downloads
- pub(crate) on KeeperFile::http_client and skip_ssl_verify — both are
  internal propagation fields with no reason to be part of the public API
- client_builder.build().ok() → build().map_err(...)? in SecretsManager::new()
  so a TLS init failure surfaces at construction time instead of deferring
  to the first file download
- extract KeeperFile::resolve_http_client() helper to eliminate duplicated
  client-building fallback in get_file_data() and get_thumbnail_data()
get_folders() and its private fetch_and_decrypt_folders() both took self
by value, consuming the SecretsManager and preventing any subsequent call
on the same instance without cloning first. Changed both to &mut self to
match the rest of the API (get_secrets, create_secret, etc.).

Also fixes a pre-existing compile error in empty_config_test.rs where
ClientOptions::new() calls were missing the proxy_url argument after
it was added to the signature.
…self

fix(rust-sdk): KSM-812 get_folders() borrows instead of consuming SecretsManager
Both Rust SDK workflows (test + publish) updated:
- Add missing integration tests present in one workflow but absent from the
  other: caching_transmission_key_tests, download_file_by_title_tests,
  duplicate_uid_notation_test, empty_config_test (+ proxy_test in publish)
- Pin actions/checkout v3 → v6 (SHA), actions-rust-lang/setup-rust-toolchain
  → SHA, manifest-cyber/manifest-github-action → SHA,
  actions/upload-artifact v4 → SHA, rust-lang/crates-io-auth-action v1 → SHA
- Add persist-credentials: false to all checkout steps (zizmor artipacked)
- Suppress secrets-outside-env for MANIFEST_TOKEN (SBOM publish, low risk,
  job already gated by test-rust-sdk)

All layers pass actionlint and zizmor (offline); Layer 4 Docker auth is
a local Keeper org enforcement, not a workflow bug.
Add check-version job at the start of the publish pipeline that hits
crates.io API before any expensive work (tests, SBOM, cargo package).
Fails fast with a clear message if the version already exists, rather
than burning ~10min of CI then failing at the upload step.

test-rust-sdk now needs: check-version so the entire pipeline gates
on the version check.
…wnload fallback

- core.rs: propagate build_proxy() error at SecretsManager construction time
  instead of silently bypassing a malformed proxy URL; customers behind
  corporate firewalls now get a clear error rather than silent direct traffic
- dtos.rs: replace Proxy::all() in resolve_http_client() fallback with full
  URL parsing + basic-auth handling matching build_proxy(); fixes credential
  drop for authenticated proxies in the KeeperFile standalone path
- dtos.rs: replace eprintln! with error!() in two file-parse error paths;
  libraries must not write to stderr directly
The test was failing intermittently in GHA but passing locally. Root cause
was env::set_var("KSM_CACHE_DIR", ...) racing with concurrent getenv calls
from other parallel tests in the same binary. set_var is unsound across
threads (marked unsafe in Rust 2024) — the symptom was save_cache()
returning Ok while the file was never written to the expected path
(read-side of the env-var got a different value than the write-side set).
Higher retry/backoff did not help because the file truly was never on
disk where cache_exists() was looking.

Moved to caching_tests.rs where every test is #[serial] (via serial_test
crate) and the workflow runs with --test-threads=1. Reuses the existing
setup_test_cache/cleanup_test_cache helpers and TEST_COUNTER for unique
directories. Dropped the retry loop since serial execution removes the
race entirely.
- Replace deprecated manifest-cyber GitHub Action (Node 20) with Syft +
  manifest CLI v0.30.0 via curl — matches pattern established in Python SDK
- Upgrade actions/upload-artifact from v4 (ea165f8, Node 20) to v7.0.1
  (043fb46, Node 24) ahead of GitHub's June 2 forced migration
- Fix retention-days: 90 → 10 to comply with repo maximum policy
…ile dep

- Update openssl to 0.10.78 in Cargo.lock — resolves 4 Critical CVEs
  (CVE-2026-41676/41677/41678/41681, CVSS 9.8/9.1) and 1 High advisory
- Remove tempfile from [dependencies] — it has no usages in src/; it was
  already present in [dev-dependencies] and was carried as dead weight.
  Removing it from the production dep graph drops r-efi (LGPL-2.1) from
  the published crate's SBOM
Matches the pattern used by publish.npm.yml and publish.nuget.yml.
Default is true (publish), uncheck to run build/test/SBOM/dry-run only.
Bumps reqwest from 0.12.28 to 0.13.3. No code changes required — the
blocking::Client API is unchanged and all tests pass.

Dependency impact:
- rustls-webpki 0.102.8 → 0.103.13: resolves GHSA-82j2-j2ch-gfr8 (High)
  and three Low/Medium CVEs (GHSA-PWJX, GHSA-XGP8, GHSA-965H)
- TLS backend: reqwest 0.13 defaults to rustls + aws-lc-rs (FIPS 140-3
  capable), replacing the previous ring-backed rustls 0.23. This is the
  required foundation for FIPS compliance in the Rust SDK.

KSM-886 fix is unaffected: the blocking client is still built once in
SecretsManager::new() and propagated to KeeperFile instances — the
structural fix that prevents nested tokio runtime creation is independent
of reqwest version.
…qwest-upgrade

chore(rust-sdk): upgrade reqwest 0.12 → 0.13.3 to resolve rustls-webpki CVEs
@stas-schaller stas-schaller changed the title feat(rust-sdk): release Keeper Secrets Manager Rust SDK v17.2.0 Release Rust SDK v17.2.0 May 1, 2026
hex 0.4.3 flagged as ABANDONED by Manifest SBOM (no activity 5 years).
data-encoding is already a direct dep used for BASE32.

- Remove hex = "0.4" from Cargo.toml
- Replace hex::encode in dtos.rs with HEXLOWER.encode
- Remove dead From<FromHexError> impl from custom_error.rs
- Update doc-comment examples in crypto.rs to use HEXLOWER
…-924)

pin-utils 0.1.0 flagged as ABANDONED by Manifest SBOM (no activity 6 years).
Removed automatically by upgrading futures-util 0.3.31→0.3.32 and
hyper 1.8.0→1.9.0. Also brings h2 0.4.7→0.4.13, http 1.2.0→1.4.0,
and ~70 other patch/minor transitive bumps.
@stas-schaller stas-schaller marked this pull request as ready for review May 1, 2026 19:17
…docs

- Remove redundant .to_string() calls in error!/warn! macro args (core.rs:934, 1183) — both were pre-existing; surfaced by first CI run of publish workflow with -D warnings
- Add Security section to v17.2.0 CHANGELOG: reqwest/rustls-webpki CVEs, openssl CVEs, aws-lc-rs TLS backend note
- Add hex->data-encoding entry to v17.2.0 Changed section (KSM-924)
- Update README installation example from 17.1.0 to 17.2.0
…SM-925)

KSM-812 changed get_folders() from consuming self to &mut self. Three
internal callsites (update_folder:1972, create_folder:2066,
create_secret:2814) still called self.clone().get_folders(), triggering
a wasted network round-trip on a throwaway instance. Remove the clones.
…t-folders

fix(rust-sdk): drop unused self.clone() in folder/secret callsites (KSM-925)
…sl_certs inversion (KSM-926)

KSM-886 introduced a shared reqwest::blocking::Client to avoid nested-runtime
panics under tokio::spawn_blocking, but only patched file downloads.
post_function (every API call) and upload_file_function (multipart uploads)
still built per-call clients and remained exposed to the same panic.

While auditing those sites, found verify_ssl_certs had been read with two
opposite semantics across the file. Concrete user-visible bug: constructing
with insecure_skip_verify=true yielded working API calls but failing multipart
uploads (cert rejected); KSM_SKIP_VERIFY=true env var produced the inverse.
After this commit the field uniformly means "do verify"; all reads route
through SecretsManager::skip_ssl_verify() — the single place that converts
verify_ssl_certs polarity to reqwest's danger_accept_invalid_certs polarity.

Both call sites now reuse self.http_client (built once in new()), removing
the panic risk and aligning TLS config across all HTTP paths.

Added four regression tests covering both insecure_skip_verify=true/false and
KSM_SKIP_VERIFY=true/false to prevent future drift in either input path.
fix(rust-sdk): share http_client across all callers; resolve verify_ssl_certs inversion (KSM-926)
Two entries under ### Fixed:
- shared http_client across post_function and upload_file_function
- verify_ssl_certs semantic inversion resolved
…on (KSM-931)

caching_post_function built a new reqwest::blocking::Client on every call,
leaving the same nested-runtime panic under tokio::spawn_blocking that
KSM-886 and KSM-926 fixed for the standard post/file paths.

- Add make_caching_post_function(client) factory that captures a pre-built
  client in a closure, eliminating Client::builder().build() from the hot path
- Refactor make_http_request to accept &Client instead of building one per call
- Keep bare caching_post_function for backward compat with a doc warning
- Change CustomPostFunction alias to Arc<dyn Fn + Send + Sync> so closures
  capturing state can be used; existing set_custom_post_function(my_fn) call
  sites are unaffected (fn implements Fn + Send + Sync + 'static)
- Add regression test in tokio::spawn_blocking proving no panic with factory

Closes KSM-931
…ttp-client

fix(rust-sdk): share reqwest::blocking::Client in caching path (KSM-931)
KSM-933: fix SSL polarity inversion when propagating config to KeeperFile
…_function

The module-level usage example still referenced caching::caching_post_function
after KSM-931 added make_caching_post_function as the safe replacement. Update
the example to show the correct API: build a reqwest::blocking::Client once
outside any async context, then pass it to make_caching_post_function.

Closes KSM-934
…unction

The Disaster Recovery Caching example and Proxy section both still showed
caching_post_function as the primary API. Update to demonstrate
make_caching_post_function with a pre-built client as the recommended path,
and clarify that KSM_PROXY_URL applies to the bare function only.

Closes KSM-934
docs(rust): update caching.rs module example to use make_caching_post_function
RecordField::new unconditionally wrapped the supplied serde_json::Value
in a single-element array, so callers passing a Value::Array (the
documented way to create multi-value fields like phone, securityQuestion,
and multi-value custom text fields) would produce [[obj1,obj2]] on the
wire instead of [obj1,obj2]. The server stored the wrong shape, causing
keeper://UID/field/phone[1] to fail with "idx out of range: 1" and
records to appear single-valued to all other SDKs and the Web Vault.

Fix: branch on the incoming value type, mirroring Python SDK behavior.
Arrays pass through unchanged; null becomes an empty array; all other
scalar/object values are wrapped in a one-element array.

Add five unit tests locking the new contract (Value::String, Number,
Object, Array-2-elems, and Null cases).
…KSM-937)

examples/manual_tests/06_caching_function.rs used the deprecated bare
caching::caching_post_function, which builds a new reqwest::blocking::Client
on every API call. Under tokio::task::spawn_blocking this panics with
"Cannot drop a runtime in a context where blocking is not allowed"
(KSM-886 root cause).

Update the example to match the module doc and README already fixed by
KSM-934: build one reqwest::blocking::Client outside any async context
and pass it to caching::make_caching_post_function(client).

Also update examples/manual_tests/README.md to reflect the correct API.
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.

Rust SecretsManager::get_folders takes self as argument instead of &mut self

2 participants