Skip to content

Conversation

@abd0-omar
Copy link
Contributor

Description

Add compile time protection against password leakage by using Password type that wraps SecretString from the secrecy crate. This ensures passwords cannot be accidentally logged.

Related Issues

Partially addresses #282 - Add protection for passwords to prevent accidental loggging

Changes Made

  • Added secrecy crate to prevent leaking passwords
  • Created Password type wrapper in libs/shared/src/models/password.rs
  • Enforces minimum 8 character constraint and can't be empty using new type pattern
  • Updated String passwords with Password type

  • Updated generate_password() to return Result<Password, PasswordGenerationError>
  • Refactored generate_password() to use byte slices instead of String for better performance
  • Added conflict detection with retry mechanism (max 10 attempts), although it's very rare for 8 characters password to conflict
  • Added proper error types: TooShort, Conflict, NonUTF8

  • Renamed parameter no_symbols to include_symbols

  • added docs in README.md

  • Upgraded schemars from 0.8.22 to 1.1.0 (with chrono04 feature, even though chrono is not used)

Screenshots

testing creating a password less than 8 characters
image
password redaction int the TUI
image
passwords in the secret.json
Screenshot 2025-12-11 192924

Breaking Changes

Minor breaking changes:

  • generate_password() now returns Result<Password, PasswordGenerationError> instead of String
  • Password fields in structs changed from Option<String> to Option<Password>
  • redact_password() changed to accept &Password instead of &str
  • redact_and_store_password() accepts Password instead of &str

if this got approved I'll work on the rest of the #282 issue

Converted SecretManager to an actor-based architecture for improved
thread-safety with benchmarks
- replace String with bytes to avoid clones
- use `choose()` to simplify getting random bytes
Remove content parameter from `redact_password()`. Unlike `redact_secrets()` which scans content, this function now only redacts CSPRNG-generated passwords.
Add `MAX_RETRIES` const to control password generation retry attempts when conflicts are detected
Add validation in `Password::new()` to reject passwords shorter
than 8 characters with `TooShort` error.
password conflicts are extremely rare so no need for all that verbosity
logging
The `Password` type ensures that passwords cannot be empty, making explicit empty checks unnecessary
Update `redact_password()` to accept the new `Password` type instead of String
@shehab299
Copy link
Contributor

@abd0-omar thank u

under review

Copy link
Collaborator

@ahmedhesham6 ahmedhesham6 left a comment

Choose a reason for hiding this comment

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

Good work on adding compile-time protection against password leakage using the secrecy crate. The Password newtype pattern is clean and the performance improvements using byte slices are nice.

Issues to Address

1. Collision detection logic only shuffles, doesn't regenerate

for attempt in 0..MAX_RETRIES {
    if redaction_map.values().any(|v| v.as_bytes() == password) {
        password.shuffle(&mut rng);  // Just shuffles same chars
        continue;
    }
    // ...
}

Shuffling the same characters is unlikely to resolve a collision since you're checking exact byte match. Consider regenerating the entire password with fresh random characters instead.

2. Missing validation in Password deserialization

The Password type validates length in new(), but serde deserialization bypasses this:

#[derive(Deserialize)]
pub struct Password(SecretString);  // Deserializes directly

A password shorter than 8 chars could be deserialized from JSON. Consider adding a custom deserializer:

impl<'de> Deserialize<'de> for Password {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where D: Deserializer<'de> {
        let s = String::deserialize(deserializer)?;
        Password::new(s).map_err(serde::de::Error::custom)
    }
}

3. Potential underflow in generate_password

If length is less than the number of required characters (4 with symbols, 3 without), this could underflow:

password.extend((0..(length - password.len())).map(...));

The minimum is enforced in Password::new(), but that happens after generation. Consider validating early:

pub fn generate_password(length: usize, ...) -> Result<Password, PasswordGenerationError> {
    if length < 8 {
        return Err(PasswordGenerationError::TooShort);
    }
    // ...
}

4. redact_password() behavioral change

The old function replaced password occurrences within content. The new function just returns a redaction key. This is fine, but make sure all callers are updated to handle this change correctly.


Thanks for the thorough work on this! The PR description and screenshots are helpful.

@abd0-omar abd0-omar force-pushed the feature/improve-password-security branch from 2d2992b to 71fc409 Compare January 2, 2026 21:23
@abd0-omar abd0-omar force-pushed the feature/improve-password-security branch from 6c91c8d to 40c151a Compare January 2, 2026 21:46
@abd0-omar
Copy link
Contributor Author

abd0-omar commented Jan 2, 2026

Thanks for the detailed review @ahmedhesham6 !

the current fixes:

  • Collision detection logic: Fixed in c0b44a1 and 8438d6d. Instead of shuffling, I've refactored the population logic into its own function.
  • Missing validation in Password deserialization: Fixed in 661e30c.
    Potential underflow in generate_password: Fixed in c412def. Added early validation.
  • redact_password behavioral change: I've kept the function as it is to just redact a password not a password within content as it was before this changes

Please let me know for any changes!

@ahmedhesham6 ahmedhesham6 changed the base branch from main to beta January 4, 2026 01:03
@ahmedhesham6
Copy link
Collaborator

@abd0-omar can you sync with beta branch

@abd0-omar
Copy link
Contributor Author

@ahmedhesham6 I've merged "actor-model", which is already merged with branch "beta", into the current branch

@ahmedhesham6 ahmedhesham6 merged commit a008b69 into stakpak:beta Jan 5, 2026
1 check passed
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