Skip to content

fix(faucet): keep keystore password in a SecretString so Debug cannot leak it#829

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/faucet-password-secretstring
Jun 23, 2026
Merged

fix(faucet): keep keystore password in a SecretString so Debug cannot leak it#829
github-actions[bot] merged 1 commit into
mainfrom
fix/faucet-password-secretstring

Conversation

@satyakwok

@satyakwok satyakwok commented Jun 23, 2026

Copy link
Copy Markdown
Member

Problem

The faucet Cli derives Debug and held the keystore password as a plain String for the whole process lifetime. Any {:?} formatting or tracing of the Cli struct would print the plaintext password.

Fix

Wrap the password in a reliakit-secret SecretString, which redacts itself in Debug/Display. The plaintext is read only at the keystore.decrypt(...) call site via expose_str().

SecretString does not implement FromStr, so a small wrap_secret clap value_parser wraps the raw arg at parse time. Behavior is otherwise unchanged: same --password flag, same SENTRIX_FAUCET_PASSWORD env var, same hide_env_values.

Test

Adds cli_debug_does_not_leak_password, which parses a Cli with a known password and asserts the plaintext does not appear in the Debug output. All faucet tests pass.

Notes

This does not change memory-zeroization behavior; reliakit-secret is redaction-only. The keystore password is consumed once at startup and the decrypted wallet is what lives for the process lifetime.

Summary by CodeRabbit

  • Bug Fixes

    • Improved password security by preventing sensitive credentials from appearing in debug output.
  • Tests

    • Added test coverage to verify password handling remains secure.

… leak it

The faucet Cli derives Debug and held the keystore password as a plain
String for the whole process lifetime, so printing or tracing the Cli
struct would expose the plaintext password. Wrap it in a reliakit-secret
SecretString, which redacts itself in Debug and Display, and read it only
at the keystore.decrypt call site via expose_str().

Adds a regression test asserting the password does not appear in the
Debug output of the parsed Cli.
@github-actions github-actions Bot enabled auto-merge (squash) June 23, 2026 12:40
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The faucet CLI is updated to handle the keystore password as a SecretString instead of a plain String. A new reliakit-secret = "1.0" dependency is added to Cargo.toml. In main.rs, the Cli.password field type changes to SecretString, and a wrap_secret function is added as a custom clap value parser that converts the raw input at parse time. The clap argument is marked as hidden in debug output. Keystore decryption now calls cli.password.expose_str(). A new unit test verifies the plaintext password does not appear in the Debug representation of Cli.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides comprehensive context (problem, fix, test, notes) but does not follow the template structure with required sections like Scope, Checks, and Deploy impact. Consider following the repository's description template by adding Scope checkboxes and other structured sections for consistency with repository standards.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main security fix: wrapping the keystore password in SecretString to prevent Debug from leaking it.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/faucet-password-secretstring

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/sentrix-faucet/src/main.rs`:
- Around line 541-548: The test at the Cli::try_parse_from call is not hermetic
because it only provides explicit values for --keystore and --password while
leaving 8 other env-backed options (--rpc-url, --listen, --drip-amount,
--chain-id, --ip-window-secs, --ip-max-drips, --addr-cooldown-secs, --tx-fee) to
be read from process environment variables. Add explicit command-line arguments
with appropriate test values for all 8 missing env-backed options to the
Cli::try_parse_from array so the test is completely isolated from the process
environment and runs predictably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08de6016-0e4b-4098-b7f8-0f9a986f288b

📥 Commits

Reviewing files that changed from the base of the PR and between e78ee3d and a2469ec.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (2)
  • bin/sentrix-faucet/Cargo.toml
  • bin/sentrix-faucet/src/main.rs

Comment on lines +541 to +548
let cli = Cli::try_parse_from([
"sentrix-faucet",
"--keystore",
"k.json",
"--password",
"hunter2-super-secret",
])
.expect("parse");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the test code at the specified lines
cd bin/sentrix-faucet && sed -n '541,548p' src/main.rs

Repository: sentrix-labs/sentrix

Length of output: 375


🏁 Script executed:

# Find the Cli struct definition to see which fields have env attributes
cd bin/sentrix-faucet && grep -n "struct Cli" src/main.rs | head -5

Repository: sentrix-labs/sentrix

Length of output: 174


🏁 Script executed:

# Search for all env = ... attributes in the Cli struct
cd bin/sentrix-faucet && ast-grep outline src/main.rs | grep -A 50 "Cli"

Repository: sentrix-labs/sentrix

Length of output: 1738


🏁 Script executed:

# Get the full Cli struct definition to see all fields and their attributes
cd bin/sentrix-faucet && sed -n '51,111p' src/main.rs

Repository: sentrix-labs/sentrix

Length of output: 2269


🏁 Script executed:

# Look for the test function that uses Cli::try_parse_from around line 541
cd bin/sentrix-faucet && sed -n '530,560p' src/main.rs

Repository: sentrix-labs/sentrix

Length of output: 979


🏁 Script executed:

# Verify clap's behavior with env attributes by checking the clap documentation or source
# Let's also check if there's a MIN_TX_FEE constant defined
cd bin/sentrix-faucet && grep -n "MIN_TX_FEE" src/main.rs | head -10

Repository: sentrix-labs/sentrix

Length of output: 378


🌐 Web query:

clap Arg env attribute behavior when not provided on command line

💡 Result:

In the clap crate, the env attribute (or the Arg::env method) specifies that if an argument is not provided on the command line, the value should be retrieved from the specified environment variable, if available [1]. If the argument is not provided on the command line and the environment variable is not set, clap will proceed to use any default values defined for the argument [1]. If no default value is defined and the argument is not marked as required, the argument will simply remain unset (or have a None value, depending on the implementation) [1][2]. Key behaviors regarding the env attribute include: 1. Integration: The env attribute requires the env feature to be enabled for the clap crate [3][4]. 2. Precedence: Values provided via the command line always take precedence over values retrieved from environment variables [1][4]. 3. Reporting: If the user does not provide the argument on the command line but the environment variable is set, clap's ArgMatches::is_present method will return true [1]. 4. Detection: To determine whether the argument was actually provided at runtime (specifically via the command line), you should use ArgMatches::occurrences_of, which will return 0 if the argument was not passed on the command line, even if it was retrieved from an environment variable or set via a default value [1].

Citations:


Make the parser regression test hermetic by providing explicit values for all env-backed options.

Cli::try_parse_from reads every omitted env = ... option from the process environment; clap checks environment variables when arguments are not provided on the command line. The test only provides --keystore and --password, leaving 8 other env-backed fields (--rpc-url, --listen, --drip-amount, --chain-id, --ip-window-secs, --ip-max-drips, --addr-cooldown-secs, --tx-fee) to potentially read from SENTRIX_FAUCET_* environment variables. Stray values in the process environment could cause the test to fail unpredictably before it validates password redaction.

Proposed fix: pass explicit values for all env-backed options
-        let cli = Cli::try_parse_from([
-            "sentrix-faucet",
-            "--keystore",
-            "k.json",
-            "--password",
-            "hunter2-super-secret",
-        ])
+        let cli = Cli::try_parse_from(vec![
+            "sentrix-faucet".to_owned(),
+            "--keystore".to_owned(),
+            "k.json".to_owned(),
+            "--password".to_owned(),
+            "hunter2-super-secret".to_owned(),
+            "--rpc-url".to_owned(),
+            "http://127.0.0.1:8545".to_owned(),
+            "--listen".to_owned(),
+            "127.0.0.1:8546".to_owned(),
+            "--drip-amount".to_owned(),
+            (100 * 100_000_000u64).to_string(),
+            "--chain-id".to_owned(),
+            "7120".to_owned(),
+            "--ip-window-secs".to_owned(),
+            "3600".to_owned(),
+            "--ip-max-drips".to_owned(),
+            "3".to_owned(),
+            "--addr-cooldown-secs".to_owned(),
+            "86400".to_owned(),
+            "--tx-fee".to_owned(),
+            MIN_TX_FEE.to_string(),
+        ])
         .expect("parse");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let cli = Cli::try_parse_from([
"sentrix-faucet",
"--keystore",
"k.json",
"--password",
"hunter2-super-secret",
])
.expect("parse");
let cli = Cli::try_parse_from(vec![
"sentrix-faucet".to_owned(),
"--keystore".to_owned(),
"k.json".to_owned(),
"--password".to_owned(),
"hunter2-super-secret".to_owned(),
"--rpc-url".to_owned(),
"http://127.0.0.1:8545".to_owned(),
"--listen".to_owned(),
"127.0.0.1:8546".to_owned(),
"--drip-amount".to_owned(),
(100 * 100_000_000u64).to_string(),
"--chain-id".to_owned(),
"7120".to_owned(),
"--ip-window-secs".to_owned(),
"3600".to_owned(),
"--ip-max-drips".to_owned(),
"3".to_owned(),
"--addr-cooldown-secs".to_owned(),
"86400".to_owned(),
"--tx-fee".to_owned(),
MIN_TX_FEE.to_string(),
])
.expect("parse");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/sentrix-faucet/src/main.rs` around lines 541 - 548, The test at the
Cli::try_parse_from call is not hermetic because it only provides explicit
values for --keystore and --password while leaving 8 other env-backed options
(--rpc-url, --listen, --drip-amount, --chain-id, --ip-window-secs,
--ip-max-drips, --addr-cooldown-secs, --tx-fee) to be read from process
environment variables. Add explicit command-line arguments with appropriate test
values for all 8 missing env-backed options to the Cli::try_parse_from array so
the test is completely isolated from the process environment and runs
predictably.

@github-actions github-actions Bot merged commit 681a799 into main Jun 23, 2026
18 checks passed
@satyakwok satyakwok deleted the fix/faucet-password-secretstring branch June 23, 2026 13:04
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.

1 participant