Skip to content

fix(sdk): replace env::set_var with direct filter in FFI logging setup#3302

Merged
lklimek merged 5 commits intov3.1-devfrom
fix/ffi-env-set-var-thread-safety
Mar 31, 2026
Merged

fix(sdk): replace env::set_var with direct filter in FFI logging setup#3302
lklimek merged 5 commits intov3.1-devfrom
fix/ffi-env-set-var-thread-safety

Conversation

@QuantumExplorer
Copy link
Copy Markdown
Member

@QuantumExplorer QuantumExplorer commented Mar 15, 2026

Issue being fixed or feature implemented

Security audit found that dash_sdk_enable_logging calls std::env::set_var("RUST_LOG", ...) which is a data race in multi-threaded contexts. Since Rust 1.66+, env::set_var is deprecated as safe because it is unsound on POSIX systems when other threads may concurrently read environment variables. The Tokio runtime may already have worker threads active when this function is called.

What was done?

  • Replaced env::set_var("RUST_LOG", ...) with direct tracing_subscriber::EnvFilter::new(filter_string) construction
  • The filter string is built in-process with the same per-crate directives (dash_sdk, rs_sdk, dapi_grpc, h2, tower, hyper, tonic) that were previously stored in RUST_LOG
  • The tracing_subscriber::fmt subscriber is initialized directly with the filter via try_init(), which is safe to call multiple times (subsequent calls are no-ops)
  • Added tracing-subscriber with env-filter and fmt features as a direct dependency of rs-sdk-ffi
  • No environment variables are read or written by the new implementation

How Has This Been Tested?

  • Added unit test enable_logging_does_not_set_env_var that verifies RUST_LOG is never set by the function
  • Added unit test enable_logging_is_thread_safe that calls the function concurrently from multiple threads to confirm no panics or data races
  • All 232 existing rs-sdk-ffi lib tests pass
  • cargo clippy -p rs-sdk-ffi clean (no warnings)
  • cargo fmt --all clean

Breaking Changes

None. The function signature and behavior are unchanged -- logging output is identical, only the internal mechanism for configuring the tracing subscriber has changed.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Logging initialization is now safe to call repeatedly, thread-safe, and will not panic if a global logger is already set.
    • Logging is configured in-process and will prefer existing external logging directives instead of overwriting environment settings.
  • Tests

    • Added tests to verify no reliance on mutating environment variables and safe concurrent initialization.
  • Documentation

    • Clarified logging behavior, thread-safety, and precedence of existing logging directives.

Replace `env::set_var("RUST_LOG", ...)` in `dash_sdk_enable_logging`
with direct `tracing_subscriber::EnvFilter` construction and subscriber
initialization. The previous approach was a data race when called after
the Tokio runtime (or any other threads) had started, since
`env::set_var` is unsound in multi-threaded POSIX processes and has been
deprecated as safe since Rust 1.66.

The new implementation constructs the same per-crate filter directives
in-process and passes them directly to `tracing_subscriber::fmt` via
`EnvFilter::new()`, which never reads or writes environment variables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d21a804-7d83-432f-9568-d845906df6a5

📥 Commits

Reviewing files that changed from the base of the PR and between 7599f5f and 69369d9.

📒 Files selected for processing (1)
  • packages/rs-sdk-ffi/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-sdk-ffi/src/lib.rs

📝 Walkthrough

Walkthrough

Replaced environment-variable-based logging with in-process tracing-subscriber setup. dash_sdk_enable_logging prefers RUST_LOG via EnvFilter::try_from_default_env(); otherwise builds a per-crate filter and calls try_init() to install a global subscriber without modifying RUST_LOG. Added tests for no-env mutation and thread-safety.

Changes

Cohort / File(s) Summary
Logging dependency
packages/rs-sdk-ffi/Cargo.toml
Added tracing-subscriber dependency with env-filter and fmt features.
Logging implementation & tests
packages/rs-sdk-ffi/src/lib.rs
Refactored dash_sdk_enable_logging to build a per-crate EnvFilter, prefer existing RUST_LOG via EnvFilter::try_from_default_env(), install subscriber with try_init() (no-op if already set), removed env::set_var usage; added unit tests ensuring RUST_LOG isn't set and initializer is thread-safe.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 I tuned the traces with gentle hops,
No env-var digging, no noisy stops.
Threads may race, the setup stays bright,
Logs find their way without a fight,
I hop off grinning into the night. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: replacing env::set_var with a direct tracing filter in FFI logging, which is the core security/soundness fix in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ffi-env-set-var-thread-safety

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added this to the v3.1.0 milestone Mar 15, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.93%. Comparing base (e398cd7) to head (69369d9).
⚠️ Report is 8 commits behind head on v3.1-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           v3.1-dev    #3302      +/-   ##
============================================
- Coverage     79.93%   79.93%   -0.01%     
============================================
  Files          2861     2861              
  Lines        280230   280236       +6     
============================================
  Hits         223993   223993              
- Misses        56237    56243       +6     
Components Coverage Δ
dpp 71.82% <ø> (ø)
drive 82.30% <ø> (ø)
drive-abci 86.67% <ø> (ø)
sdk 36.55% <ø> (ø)
dapi-client 79.06% <ø> (ø)
platform-version ∅ <ø> (∅)
platform-value 83.36% <ø> (ø)
platform-wallet 76.09% <ø> (-0.46%) ⬇️
drive-proof-verifier 55.26% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 15, 2026

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "7f4d96d07d78bf6fda77bf80eb0586cdea652cbda5b27323796c1df1e578d4de"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@QuantumExplorer QuantumExplorer requested a review from lklimek March 15, 2026 14:58
@QuantumExplorer
Copy link
Copy Markdown
Member Author

@lklimek can you review this one?

@QuantumExplorer QuantumExplorer added the help wanted Extra attention is needed label Mar 15, 2026
@PastaPastaPasta
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/rs-sdk-ffi/src/lib.rs (1)

206-214: Log message may be misleading when subscriber is already installed.

When try_init() returns Err (subscriber already exists), the "logging enabled" message is still emitted via the existing subscriber, not the one we tried to create. This can mislead callers into thinking our per-crate filter was applied.

Per the context snippets, rs-drive-abci may initialize its own subscriber via try_init() before this FFI function runs, making this scenario likely in integrated deployments.

Consider either:

  1. Conditionally logging based on try_init() result, or
  2. Adjusting the message to clarify when the filter was actually applied.
♻️ Suggested improvement
-    // Initialize the global tracing subscriber.  `try_init` returns Err if a
-    // subscriber is already installed; we intentionally ignore that so that
-    // calling this function more than once is harmless.
-    let _ = tracing_subscriber::fmt::fmt()
+    // Initialize the global tracing subscriber. `try_init` returns Err if a
+    // subscriber is already installed; we intentionally ignore that so that
+    // calling this function more than once is harmless.
+    let installed = tracing_subscriber::fmt::fmt()
         .with_env_filter(filter)
-        .try_init();
+        .try_init()
+        .is_ok();
 
-    tracing::info!(level = log_level, "logging enabled");
+    if installed {
+        tracing::info!(level = log_level, "logging enabled with per-crate filter");
+    } else {
+        tracing::debug!(level = log_level, "subscriber already installed; per-crate filter not applied");
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk-ffi/src/lib.rs` around lines 206 - 214, The current code
unconditionally emits tracing::info! after calling
tracing_subscriber::fmt().with_env_filter(filter).try_init(), which can mislead
when try_init() returns Err (subscriber already installed); update the init
sequence to capture the Result from
tracing_subscriber::fmt().fmt().with_env_filter(filter).try_init() and
conditionally log: on Ok(...) emit the existing "logging enabled" message
(including the applied filter or log_level if desired), and on Err(...) emit a
clear message that the subscriber was already installed and the per-crate filter
was not applied (or that existing settings remain); use the try_init() result to
drive which message is logged so callers of the FFI see accurate information.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/rs-sdk-ffi/src/lib.rs`:
- Around line 206-214: The current code unconditionally emits tracing::info!
after calling tracing_subscriber::fmt().with_env_filter(filter).try_init(),
which can mislead when try_init() returns Err (subscriber already installed);
update the init sequence to capture the Result from
tracing_subscriber::fmt().fmt().with_env_filter(filter).try_init() and
conditionally log: on Ok(...) emit the existing "logging enabled" message
(including the applied filter or log_level if desired), and on Err(...) emit a
clear message that the subscriber was already installed and the per-crate filter
was not applied (or that existing settings remain); use the try_init() result to
drive which message is logged so callers of the FFI see accurate information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a90f1cad-073c-4a0e-8ea7-eb4b6f683a7c

📥 Commits

Reviewing files that changed from the base of the PR and between dc17af2 and a8b5e7e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • packages/rs-sdk-ffi/Cargo.toml
  • packages/rs-sdk-ffi/src/lib.rs

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Solid security fix replacing unsafe env::set_var with direct EnvFilter construction. The core change is correct and thread-safe. Two real issues: the filter omits the FFI crate's own tracing target (rs_sdk_ffi), and the regression test reintroduces the same class of unsafe env mutation this PR eliminates.

🟡 2 suggestion(s) | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-sdk-ffi/src/lib.rs`:
- [SUGGESTION] lines 197-200: FFI crate's own tracing events are silently filtered out
  The filter string includes `rs_sdk` but not `rs_sdk_ffi`. In tracing's EnvFilter, `rs_sdk` matches targets like `rs_sdk::foo` (child modules delimited by `::`) but does NOT match `rs_sdk_ffi` — that's a distinct crate target. This means `tracing::info!(...)` at line 213 (implicit target `rs_sdk_ffi`) is silently dropped, and any future diagnostics emitted from this crate are invisible. The old code had the same omission (RUST_LOG also lacked rs_sdk_ffi), so this isn't a regression, but since we're rebuilding the filter from scratch it's a good opportunity to fix it.
- [SUGGESTION] lines 231-239: Regression test reintroduces unsafe env mutation that the PR is eliminating
  The test calls `unsafe { std::env::remove_var("RUST_LOG") }` with a safety comment claiming the binary is single-threaded. This is incorrect: `cargo test` runs `#[test]` functions on parallel threads by default, so `enable_logging_does_not_set_env_var` may execute concurrently with `enable_logging_is_thread_safe` (which itself spawns additional threads). This is the same class of data race the PR is fixing.

Since the new implementation never touches RUST_LOG, the test can simply assert that `std::env::var("RUST_LOG").is_err()` without the remove_var preamble — or guard with `env::var_os` before asserting and skip if already set.

Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs Outdated
- Add rs_sdk_ffi to the tracing filter string so the FFI crate's own
  events are not silently dropped
- Only emit "logging enabled" info log on successful try_init (not on
  idempotent re-calls where the level is silently discarded)
- Remove unsafe env::remove_var from test — skip instead if RUST_LOG
  is already set, avoiding the same data race class the PR is fixing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean follow-up addressing all three prior review findings. The filter string now includes rs_sdk_ffi, the tracing::info! call is correctly gated on successful subscriber initialization, and the test no longer uses unsafe { env::remove_var } — replaced with a safe early-return guard. No new issues.

Reviewed commit: 1ea0a91

Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs
@lklimek lklimek self-assigned this Mar 20, 2026
lklimek and others added 2 commits March 20, 2026 09:17
Use try_from_default_env() to honor RUST_LOG when set, falling back to
the hardcoded per-crate filter. This reads but never writes env vars,
preserving thread safety. Also clarify that tracing-subscriber's
built-in tracing-log bridge handles log crate compatibility without
needing a separate env_logger init.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
lklimek
lklimek previously approved these changes Mar 20, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the rs-sdk-ffi logging initialization to avoid std::env::set_var("RUST_LOG", ...) (which is unsound in multi-threaded contexts) by configuring a tracing_subscriber filter directly inside the FFI entrypoint.

Changes:

  • Replaced RUST_LOG mutation with an in-process tracing_subscriber::EnvFilter + fmt subscriber initialization.
  • Added tracing-subscriber as a direct dependency of rs-sdk-ffi with env-filter and fmt features.
  • Added unit tests asserting dash_sdk_enable_logging doesn’t set RUST_LOG and can be called concurrently.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/rs-sdk-ffi/src/lib.rs Builds an explicit filter string and initializes a global tracing subscriber via try_init(), plus adds unit tests.
packages/rs-sdk-ffi/Cargo.toml Adds tracing-subscriber dependency needed by the new logging setup.
Cargo.lock Locks the new tracing-subscriber dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs
Comment thread packages/rs-sdk-ffi/src/lib.rs Outdated
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean, well-targeted security fix. Replaces the unsafe env::set_var("RUST_LOG") data race with direct tracing_subscriber::EnvFilter construction and try_init() for idempotent subscriber registration. All six review agents (general, security, FFI) independently confirm the fix is correct: the data race is eliminated, try_init() is the right choice for FFI, repeated calls are safe no-ops, and RUST_LOG is still respected via read-only try_from_default_env(). The rs_sdk_ffi crate was also added to the filter directives (previously missing). Tests cover both the no-env-mutation guarantee and concurrent thread safety.

Reviewed commit: 7599f5f

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lklimek lklimek merged commit 739754b into v3.1-dev Mar 31, 2026
40 checks passed
@lklimek lklimek deleted the fix/ffi-env-set-var-thread-safety branch March 31, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants