Release Rust SDK v17.2.0#993
Open
stas-schaller wants to merge 41 commits intomasterfrom
Open
Conversation
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
…097), reqwest 0.12.24→0.12.28
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
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
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.
…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
…st_function (KSM-926)
…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.
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
Rust SDK v17.2.0 — bug fix release.
Bug Fixes
KSM-886: Fixed
get_file_data()andget_thumbnail_data()failing with "builder error" when called from insidetokio::spawn_blocking. Root cause:reqwest::blocking::Clientcreates 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 inSecretsManager::new()and propagate it toKeeperFileobjects. (#991)KSM-812: Fixed
get_folders()consuming theSecretsManagerinstance (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 callself.get_folders()directly. (#1002)KSM-926:
post_function(every API call) andupload_file_function(multipart file upload) each built a newreqwest::blocking::Clientper call, leaving the same nested-runtime panic risk undertokio::spawn_blockingthat KSM-886 fixed for file downloads. Both callsites now reuse the shared client built inSecretsManager::new(). Also resolved a long-standing semantic inversion ofverify_ssl_certs— the field was assigned and read with opposite conventions across two code paths. The field now uniformly means "do verify"; alldanger_accept_invalid_certscalls route through a newskip_ssl_verify()accessor. Net behavior:insecure_skip_verify=trueandKSM_SKIP_VERIFY=truenow both correctly relax TLS on every HTTP path; the default is strict on every HTTP path. (#1003)KSM-931:
caching_post_functionbuilt a newreqwest::blocking::Clienton every call, leaving the same nested-runtime panic undertokio::spawn_blockingthat KSM-886/926 fixed for the standard paths. Newcaching::make_caching_post_function(client)factory captures a pre-built client in a closure and reuses it across calls. The barecaching_post_functionis retained for synchronous callers with a doc warning. (#1004)KSM-933:
get_secrets()propagatedSecretsManager.verify_ssl_certs(positive-sense: true = strict) directly intoKeeperFile.skip_ssl_verify(negative-sense: true = skip), inverting TLS intent on every file attachment in strict mode. Both propagation sites now useself.skip_ssl_verify(), which correctly returns!verify_ssl_certs. Currently masked by the sharedhttp_clientpath 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 inREADME.md, and the Proxy section inREADME.mdall still pointed users atcaching_post_functionafter KSM-931 introducedmake_caching_post_functionas its safe replacement. All three updated to showmake_caching_post_function(client)as the primary API; bare function retained with non-async caveat in the Proxy section. (#1006)KSM-936:
RecordField::newunconditionally wrapped the suppliedValuein a single-element array, so callers passing aValue::Array(the documented way to create multi-value fields likephoneandsecurityQuestion) produced[[obj1, obj2]]on the wire instead of[obj1, obj2]. The server stored the wrong shape, causingkeeper://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.rsstill used the deprecated barecaching::caching_post_functionafter 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 onereqwest::blocking::Clientoutside any async context and pass it tocaching::make_caching_post_function(client).Internal / Housekeeping
KeeperFile::http_clientandskip_ssl_verifyfields narrowed frompubtopub(crate)— implementation details with no external use caseclient_builder.build().ok()→build().map_err(...)?inSecretsManager::new()— TLS init failures now surface at construction timeKeeperFile::resolve_http_client()helper to eliminate duplicated client-building fallbackempty_config_test.rs(missingproxy_urlargument inClientOptions::new()calls)Breaking Changes
get_folders()signature changed fromselfto&mut self. Callers that relied on the consuming signature must remove any.clone()added to work around the original bug.CustomPostFunctiontype alias changed fromfn(...)toArc<dyn Fn(...) + Send + Sync>. Code that stored the alias directly must wrap withArc::new(...). Call sites usingoptions.set_custom_post_function(my_fn)are unaffected —set_custom_post_functionnow acceptsimpl Fn(...)and wraps internally.Related Issues