Skip to content

Database keys encryption#475

Merged
grunch merged 45 commits into
mainfrom
feature-db-encryption
May 29, 2025
Merged

Database keys encryption#475
grunch merged 45 commits into
mainfrom
feature-db-encryption

Conversation

@arkanoider

@arkanoider arkanoider commented Apr 14, 2025

Copy link
Copy Markdown
Collaborator

Hi @grunch,

I will drop some infos here about the idea of encryption on the identity keys in mostro database tables to decouple in a more definitive way relations between orders and nostr users.

I will use these two crates for the encryption part:

Argon2 is a famous and reccomended KDF, with it I will create a hashed value of the user password to be safely saved in the mostro database.

Cha Cha is a valid and quick algo for ciphering the keys that we want in the db.

Below a simple flowchart of what will happens when mostro will be started from admin:

flowchart TD
    A[Start Mostro] -->|mostro.db file <br>exists| B(Ask user database<br> password )
    A[Start Mostro] -->|mostro.db file <br>not exists| C(Ask user a strong<br> password)
    C --> D[Double check <br>password]
    D --> |check failed|C
    D --> |valid password|E[Password saved in mostro.db]
    B --> |read password<br>from user|F[compare with hashed<br>password]
    F --> |valid password|G[Start mostro with encryption]
    F --> |not valid password|B
Loading

My initial idea is to save a new user, who is the admin in this special case, when mostro is run for the first time. I will add a new string field in users table ( optional - I will let also user give an empty string to have no encryption in case ) and this will be the admin password hashed in the format of Argon2, for those who wants to play a bit with argon2 this is a nice online site with a generator:

Argon2 generator online

A typical stored string is:

$argon2d$v=19$m=1024,t=1,p=1$c2FsdHlzYWx0eXNhbHR5$VucvoVfuAxUpW4FTuc+8JbgZYBRnw5b/41AJugHoIkY

When admin password is in place, with Argon2 crate is easy to check against the user password with a verify_password method.

User password is now set in RAM using a rust OnceCell so it's just inited once and then will be used during encrypt/decrypt operations over the keys in database. I have introduced also another small crate called secrecy that helps avoiding leak of password in RAM in the logs/debug messages.

I am still working on this but basically login part seems working ( still debugging edge cases...) than I will update also mostro-core with the method to ecnrypt/decrypt the requested data.

Summary by CodeRabbit

  • New Features

    • Introduced password-based encryption and authentication for the database with strong password policies and secure handling.
    • Added encrypted storage and verification of admin passwords for database access.
    • Master buyer and seller public keys are now encrypted in storage and decrypted when used in orders and reputation processes.
    • Enhanced privacy for user identity information in orders, disputes, and messaging.
    • Added terminal screen clearing on startup for improved user experience.
  • Bug Fixes

    • Improved error handling and logging for better traceability and reliability across the application.
  • Refactor

    • Consolidated imports and updated module paths for cleaner code organization.
    • Streamlined dispute handling, event creation, and user presence verification logic.
    • Updated scheduler job error handling and logging for clarity.
    • Simplified subscription filter usage and optimized event sending by passing references.
  • Chores

    • Updated dependencies to support encryption, secure password input, and terminal clearing.
    • Refined configuration file initialization and CLI documentation for better clarity.
    • Restricted database file permissions on Unix systems for enhanced security.
  • Tests

    • Added tests verifying encryption and decryption of database-stored data.

@arkanoider arkanoider requested review from Catrya and grunch April 14, 2025 09:43
@coderabbitai

coderabbitai Bot commented Apr 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This update introduces password-based encryption for sensitive database fields and enforces strong password authentication for database access. It adds new dependencies to support encryption, secure password handling, and terminal management. The database schema and access logic are refactored to handle encrypted public keys, and password prompts are integrated into the database connection flow. Several modules are updated to support these changes.

Changes

File(s) Change Summary
Cargo.toml Updated dependencies: bumped nostr-sdk version, bumped mostro-core version, added rpassword, argon2, secrecy, and clearscreen. No removals or feature changes.
migrations/20231005195154_users.sql Added admin_password char(64) column to the users table.
src/db.rs Implemented password-based encryption/authentication: prompts for strong password on DB creation, enforces password strength, stores password hash, restricts DB file permissions, and authenticates on access. Refactored queries to handle encrypted fields, added test for encryption/decryption, improved error handling, and updated several public functions to support encrypted data and password logic.
src/main.rs Added config module, global static MOSTRO_DB_PASSWORD, and terminal clearing at startup. Updated imports and logging. Added TODO for DB pool sharing.
src/app/take_buy.rs
src/app/take_sell.rs
src/util.rs
Changed storage of master buyer/seller pubkeys to use encryption with the database password. Updated relevant imports and error handling. In util.rs, also decrypts keys when needed and adds logging.
src/app/trade_pubkey.rs Decrypts master buyer/seller pubkeys before comparison with event sender; updates logic to handle encrypted keys and error mapping.
src/app/admin_take_dispute.rs Refactored dispute handling: uses decrypted keys, updates message construction, uses event sender for solver/admin, improves error handling, and updates event sending logic. Function signatures updated to receive keys as parameter.
src/app.rs Enhanced error logging, improved signature verification by deserializing rumor content to (Message, Option<Signature>), updated admin dispute action call, and switched to structured logging.
src/app/rate_user.rs Updated imports and changed how master pubkeys are retrieved (now using password). Simplified user presence check.
src/app/release.rs Updated imports, error handling, and function signatures. Handles encrypted keys, passes password to key retrieval functions, and adjusts event/tag creation logic.
src/scheduler.rs Updated import paths, changed event sending to use references, improved logging, changed error handling in scheduled jobs to log and return early, and updated client creation logic.
src/cli.rs Refactored settings_init to return Result<(), Box<dyn std::error::Error>>, added documentation, and updated config file initialization logic and imports.
src/config/util.rs Reformatted error construction for missing home directory in init_configuration_file for clarity.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Terminal
    participant Main
    participant DB as Database
    participant Crypto as CryptoUtils

    User->>Terminal: Start application
    Main->>Terminal: Clear screen
    Main->>User: Prompt for DB password (if new DB)
    User->>Main: Enter password
    Main->>Crypto: Validate password strength
    Crypto-->>Main: OK/Fail
    alt New DB
        Main->>Crypto: Hash password
        Main->>DB: Store admin password hash
        Main->>DB: Restrict file permissions
        Main->>Crypto: Store password in MOSTRO_DB_PASSWORD
    else Existing DB
        loop Up to 3 attempts
            Main->>User: Prompt for password
            User->>Main: Enter password
            Main->>DB: Retrieve stored hash
            Main->>Crypto: Verify password
            Crypto-->>Main: OK/Fail
        end
        alt Password correct
            Main->>Crypto: Store password in MOSTRO_DB_PASSWORD
        else
            Main->>Terminal: Exit (authentication failed)
        end
    end
    Main->>DB: Open connection (with password)
Loading

Possibly related PRs

  • Rate user feature #404: Updates user-related database handling and user rating features, with overlapping changes in user presence and rating updates.
  • Feature disputes #463: Modifies admin_take_dispute_action in a similar way, updating dispute handling and user info retrieval.
  • Settings refactor #481: Refactors configuration handling and import paths related to Settings struct, closely related to config module changes in this PR.

Suggested reviewers

  • grunch
  • Catrya

Poem

In burrows deep, the passwords hide,
Encryption now stands by our side.
With keys held close and secrets tight,
Our data hops from byte to byte.
The database safe, the warnings clear—
Mostro’s future, secure and dear!
🐰🔐✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 444b6ce and fec0d3d.

📒 Files selected for processing (1)
  • src/app/release.rs (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/release.rs
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@fabohax

fabohax commented Apr 14, 2025

Copy link
Copy Markdown

What do you think about to wrap the current hex sec key that user could have and multiply it by the password: That would be strong enough, don't?

@arkanoider arkanoider force-pushed the feature-db-encryption branch from 20ae674 to 7da854e Compare April 18, 2025 15:50
@arkanoider arkanoider changed the base branch from main to develop April 18, 2025 15:50
@arkanoider

Copy link
Copy Markdown
Collaborator Author

@grunch @Catrya

UPDATE:

Trying to execute the flow of the orders I have found some possible limitations, mostly caused by the fact the encryption/decryption requires CPU time.

Examples:

Now when a message comes in Mostrod we check if the user is new and wants to be rated, in that case if it's index0 key (identity key) is not present we add it. If we encrypt the key what happens next? Let's suppose we have 1000 user in the db all their keys are encrypted and when one of them sends a new order we have to check that he's yet in the database list. To check we must decrypt all the 1000 keys and check against the pubkey in the message of the client --> SLOW and it grows with number of users

My idea is to treat only orders table of database like this:

image

master_buyer_pubkey and master_seller_pubkey in the image are encrypted using a derived key with argon and chacha cyphered them, finally they were base64 encoded to have strings. Doing that we detach users table identity key ( index0) from orders table so orders cannot be related to their taker/maker index0 key.

In this way I am yet able to do simple order flow and timings seems acceptable. There is still two points in takesell/takebuy where we need to check against some key and it's when we call:

buyer_has_pending_order

seller_has_pending_order

in these case we have to extract the list of order in a particular state and check if user is having orders pending so we should cycle decrypting some keys, will check if we can improve the check in some ways, but i think it's tolerable.

Ok will go on testing orders cycle, but I am glad to have your opinions.

@arkanoider

arkanoider commented Apr 24, 2025

Copy link
Copy Markdown
Collaborator Author

@grunch ,

I added a nice ( I think... ) idea for caching keys and speed up search when mostro is scanning the vector of index0 keys encrypted and we have to check if one is yet present:

Cache Argon2 Key Derivations
Using the password and the same salt i get the same encryption key with Argon2, so if I can cache some combinations of salt+password I can use a HashMap<(Salt+Password), DecryptKey> to get the keys for decryption in zero time from RAM.

This works because for a given (password, salt), the derived key is always the same.

I have created a tests with good results, these inserts 10 encrypted string and then some are decrypted normally and some are cached:

running 1 test
In-memory database and table created for test.
Inserting 10 entries...
Inserting value Entry 0
Salt: 3bLfcYlFRs41Jm/6Byd46A

Inserting value Entry 1
###Salt: c2FsdE5QZXBwYVBlcHBhMQ

Inserting value Entry 2
Salt: bi4gVvI4Na9xfQiDvxtUMw
Inserting value Entry 3
Salt: pHnzGJzxZDUisXV/D8CQng

Inserting value Entry 4
###Salt: c2FsdE5QZXBwYVBlcHBhMQ

Inserting value Entry 0
Salt: dHtIk1RRv+BDTU7NYi0rKw
Inserting value Entry 1
Salt: zCO424lJY0Kvo71J8YJjsw

Inserting value Entry 2
###Salt: c2FsdE5QZXBwYVBlcHBhMQ

Inserting value Entry 3
Salt: SX1JLNbyhQwc52njNWD9bA
Inserting value Entry 4
Salt: f5YLnA46iuOdOjBG4qbt2Q
Entries inserted.
Fetching 'value' column...
Cache key: 16609968908560343058
Key not found in cache, creating new one! 16609968908560343058
Time taken to decrypt: 289 ms
Cache key: 14650601133528733758
Key not found in cache, creating new one! 14650601133528733758
Time taken to decrypt: 209 ms
Cache key: 7000585519848012028
Key not found in cache, creating new one! 7000585519848012028
Time taken to decrypt: 194 ms
Cache key: 11579821871313292092
Key not found in cache, creating new one! 11579821871313292092
Time taken to decrypt: 226 ms
Cache key: 14650601133528733758

###Key found in cache!
###Time taken to decrypt: 0 ms

Cache key: 7881818280167815519
Key not found in cache, creating new one! 7881818280167815519
Time taken to decrypt: 278 ms
Cache key: 16204407719138663129
Key not found in cache, creating new one! 16204407719138663129
Time taken to decrypt: 269 ms
Cache key: 14650601133528733758

###Key found in cache!
###Time taken to decrypt: 0 ms

Cache key: 8474487473980109098
Key not found in cache, creating new one! 8474487473980109098
Time taken to decrypt: 264 ms
Cache key: 14569125712987388781
Key not found in cache, creating new one! 14569125712987388781
Time taken to decrypt: 258 ms
Entry 0 found in the hash set.
Test passed!
test db::tests::test_fetch_string_column_scalar ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 13 filtered out; finished in 4.32s

You can see that there is an insertion of three entries with the same salt and the result in decryption that two are with 0 time because the combination of Salt+Password was in cache. I used queue of 50 elements now to start, but it is just a number we can also increase it.

So in this way typically new user will have the first time some delay due to encrytpion decryption, but than with cache they are super fast response.

I tested just locally with rust tests, tonite I will try this in a real scenario while completing flows of orders.

@arkanoider arkanoider marked this pull request as ready for review April 26, 2025 08:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/util.rs (1)

406-411: Same misleading error mapping for seller side

Please apply the same fix here as in the buyer branch to keep error semantics consistent.

🧹 Nitpick comments (6)
src/main.rs (1)

75-75: Consider implementing the database connection pooling suggestion

The TODO comment correctly identifies a potential improvement. Moving the database connection pool to a shared reference-counted container would prevent database reinitialization across threads.

Consider implementing this using an Arc<Pool> stored in a static OnceLock, similar to how MOSTRO_DB_PASSWORD is managed.

src/app/take_sell.rs (1)

53-55: Consider performance impact of buyer_has_pending_order.

Based on the relevant code snippets from src/db.rs, the buyer_has_pending_order function now needs to decrypt all pending orders' master buyer pubkeys to check for matches. This could cause performance issues as the number of pending orders grows.

Consider implementing a caching mechanism similar to the one mentioned in the PR comments, or explore indexing strategies that would minimize the number of decryptions needed.

src/util.rs (1)

548-552: Left-over debugging println!

The println! statement will spam STDOUT in production.
Switch to tracing::debug! (or remove entirely).

-    println!(
-        "Inside update_order_event order_updated status {:?} - order id {:?}",
-        order_updated.status, order_updated.id,
-    );
+    tracing::debug!(
+        "update_order_event → status {:?} - order id {:?}",
+        order_updated.status,
+        order_updated.id,
+    );
src/db.rs (3)

161-201: Unnecessary confirmation prompt when password is skipped

If the admin presses to skip encryption you still prompt for confirmation, forcing the user to hit twice.

Add an early return when password.is_empty() to streamline UX:

-        if password.is_empty() {
-            println!("Password skipped!!");
-            break;
-        } else {
-            // Confirm password
-            print!("Confirm database password: ");
-        }
+        if password.is_empty() {
+            println!("Password skipped – database will stay unencrypted.");
+            break;
+        }
+        // Confirm password
+        print!("Confirm database password: ");

300-338: Redundant double parsing of Argon2 hash

hash_password already returns a PasswordHash string ready for storage; immediately parsing it again and converting back to string adds cost and cognitive load.

-    let key = argon2
+    let password_hash = argon2
         .hash_password(password.expose_secret().as_bytes(), &salt)?
         .to_string();
-
-    // Create a key suitable for ChaCha20Poly1305
-    let key = PasswordHash::new(&key)?.to_string();

Store password_hash directly.


35-50: Permission hardening bypassed on Windows

On Windows the function silently prints a warning but still returns Ok(()), leaving the DB wide-open.
Either:

  1. Abort the start-up when secure permissions cannot be set, or
  2. Provide an actionable message (e.g., “run icacls ...”) and clearly mark the instance as unsafe.

Fail-fast is preferable to avoid a false sense of security.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fb8de6 and c769af7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml (1 hunks)
  • migrations/20231005195154_users.sql (1 hunks)
  • src/app.rs (1 hunks)
  • src/app/take_buy.rs (2 hunks)
  • src/app/take_sell.rs (2 hunks)
  • src/app/trade_pubkey.rs (2 hunks)
  • src/db.rs (9 hunks)
  • src/main.rs (4 hunks)
  • src/scheduler.rs (2 hunks)
  • src/util.rs (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/app/take_sell.rs (1)
src/db.rs (2)
  • buyer_has_pending_order (821-870)
  • update_user_trade_index (735-767)
🔇 Additional comments (12)
src/scheduler.rs (2)

207-207: LGTM! Improved log message clarity

Converting seconds to minutes in the log message makes it more readable for users.


233-253: LGTM! Simplified error handling pattern

The error handling has been improved by replacing the Result return type with early returns and explicit error logging, which is more consistent with the codebase's error handling approach.

src/app/take_buy.rs (1)

70-74: LGTM! Implemented encryption for seller's public key

The seller's public key is now properly encrypted before storage, improving privacy by decoupling user identities from orders. The use of MOSTRO_DB_PASSWORD for encryption aligns with the PR objectives.

src/main.rs (2)

35-35: LGTM! Implemented secure password storage

Using OnceLock<SecretString> for MOSTRO_DB_PASSWORD provides thread-safe initialization and secure memory handling for the database password.


49-51: LGTM! Security improvement for password entry

Clearing the terminal screen before startup prevents sensitive information like passwords from being visible in terminal scrollback history.

Cargo.toml (1)

49-52: New dependencies align with encryption requirements.

The additions of rpassword, argon2, secrecy, and clearscreen are appropriate for implementing the password-based encryption system described in the PR objectives. These libraries are well-established choices for their respective functions.

src/app/trade_pubkey.rs (2)

28-49: Key decryption logic properly handles both buyer and seller cases.

The implementation correctly decrypts either the master buyer or master seller public key based on which one exists in the order. Error handling is comprehensive with appropriate error mapping.


51-59: New code correctly matches decrypted keys.

The matching logic properly uses the decrypted keys when comparing against the event sender's public key, maintaining the intended functionality while accommodating the new encryption scheme.

src/app.rs (1)

52-72: Enhanced error logging improves diagnostics.

The improved warning_msg function now provides more detailed logging by extracting specific error messages from various ServiceError variants. This will be particularly helpful for diagnosing encryption/decryption issues in production.

src/app/take_sell.rs (2)

87-91: Master buyer pubkey is now properly encrypted.

The implementation correctly uses the store_encrypted function to encrypt the buyer's identity public key before storing it in the database. The error handling is appropriate, mapping encryption errors to database access errors.


5-5: Import additions support the encryption logic.

The new imports correctly bring in the necessary components: MOSTRO_DB_PASSWORD for access to the encryption key, and store_encrypted function from the order module.

Also applies to: 7-7, 11-11

src/db.rs (1)

123-145: ⚠️ Potential issue

Password left un-zeroised after verification

After a successful comparison the plaintext password remains on the stack.
Call password.zeroize() once it has been stored in MOSTRO_DB_PASSWORD.

         if MOSTRO_DB_PASSWORD.set(SecretString::from(password)).is_ok() {
+            // wipe stack copy
+            password.zeroize();
             Ok(true)

Likely an incorrect or invalid review comment.

Comment thread migrations/20231005195154_users.sql
Comment thread Cargo.toml Outdated
Comment thread src/util.rs
Comment thread src/util.rs
Comment thread src/db.rs Outdated
@arkanoider

arkanoider commented Apr 26, 2025

Copy link
Copy Markdown
Collaborator Author

Ok! @grunch @Catrya

Opened for review, just delete your mostro.db file from .mostro folder and run mostro with locally with mostro core on this pull request

I will look to rabbit comments asap.

Comment thread src/util.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/db.rs (2)

218-236: Blocking STDIN in an async context can stall the reactor

get_user_password() performs synchronous read_password() calls inside the
async connect() path. On Tokio’s multithreaded runtime this blocks an
executor thread and may introduce latency spikes.

Consider isolating the prompt in a tokio::task::spawn_blocking closure or
moving database initialisation to a dedicated synchronous bootstrap step
executed before the async runtime starts.


784-816: Linear scan & decrypt in hot path may hurt performance

Both seller_has_pending_order and buyer_has_pending_order fetch all
pending orders and decrypt each key to find a match.
With a large order table this becomes O(n × decrypt_cost).

Ideas:

  1. Store a deterministic, non-reversible hash (e.g. SHA-256) of the master
    pubkey next to the ciphertext and index that column; compare hashes before
    decrypting.
  2. Keep an in-memory HashSet of decrypted keys populated at startup and
    updated through triggers/callbacks.

This keeps O(1) lookup per request while preserving on-disk secrecy.

src/app/rate_user.rs (1)

101-104: Avoid moving counterpart when borrowing suffices

is_user_present only needs an immutable reference to the key; passing the
owned String forces an unnecessary move.

-    let mut user_to_vote = is_user_present(pool, counterpart)
+    let mut user_to_vote = is_user_present(pool, counterpart.clone())

Minor, but keeps counterpart available for future use without another clone.

src/app/release.rs (1)

397-405: identity_pubkey is moved before reuse

identity_pubkey is moved into is_user_present; if later logic needs the
same value (e.g. for logging), it will be unavailable.

-        let user = crate::db::is_user_present(&pool, identity_pubkey)
+        let user = crate::db::is_user_present(&pool, identity_pubkey.clone())

This preserves the original string for any subsequent operations at negligible
cost.

src/app/admin_take_dispute.rs (1)

30-37: Consider caching the global password retrieval

MOSTRO_DB_PASSWORD.get() is inexpensive, but the expression appears several
times in tight loops. Binding it once improves readability and avoids repeated
calls.

let db_pwd = MOSTRO_DB_PASSWORD.get();
let master_buyer_key  = order.get_master_buyer_pubkey(db_pwd)?;
// …

Purely stylistic; feel free to skip if you prefer the explicit calls.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ce36d6 and d2252eb.

📒 Files selected for processing (8)
  • src/app/admin_take_dispute.rs (6 hunks)
  • src/app/rate_user.rs (4 hunks)
  • src/app/release.rs (2 hunks)
  • src/app/take_buy.rs (2 hunks)
  • src/app/take_sell.rs (2 hunks)
  • src/app/trade_pubkey.rs (2 hunks)
  • src/db.rs (9 hunks)
  • src/util.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/app/trade_pubkey.rs
  • src/app/take_buy.rs
  • src/app/take_sell.rs
  • src/util.rs
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/app/rate_user.rs (1)
src/db.rs (1)
  • is_user_present (689-704)
src/app/admin_take_dispute.rs (1)
src/db.rs (1)
  • is_user_present (689-704)
src/app/release.rs (1)
src/db.rs (1)
  • is_user_present (689-704)
src/db.rs (2)
src/cli/settings.rs (2)
  • new (127-154)
  • get_db (164-166)
src/util.rs (1)
  • get_keys (468-478)

Comment thread src/db.rs Outdated
Comment thread src/db.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/db.rs (4)

53-120: Password requirements implementation follows security best practices.

The password validation logic implements strong requirements (12+ chars, mixed case, digits, special chars) with clear error messages. The implementation allows bypassing requirements for empty passwords, which appears to be an intentional design choice to make encryption optional.

Consider using a more specific check for special characters rather than !c.is_alphanumeric() which also includes spaces and other non-printing characters:

-        if self.requires_special && !password.chars().any(|c| !c.is_alphanumeric()) {
+        if self.requires_special && !password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?/".contains(c)) {

273-294: Secure authentication with retry limits for existing databases.

The implementation provides a good security practice of limiting password attempts to prevent brute force attacks, with appropriate termination on repeated failures.

Consider using a cleaner loop structure for retry attempts:

-        let max_attempts = 3;
-        let mut attempts = 0;
-
-        if MOSTRO_DB_PASSWORD.get().is_none() {
-            while let Some(argon2_hash) = get_admin_password(&conn).await? {
-                // Database already exists - and yet opened
-                let parsed_hash = PasswordHash::new(&argon2_hash)
-                    .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
-                if check_password_hash(&parsed_hash).is_ok() {
-                    break;
-                } else {
-                    attempts += 1;
-                    println!("Wrong password, attempts: {}", attempts);
-                    if attempts >= max_attempts {
-                        println!("Maximum password attempts exceeded!!");
-                        std::process::exit(1);
-                    }
-                }
-            }
-        }
+        const MAX_ATTEMPTS: usize = 3;
+        
+        if MOSTRO_DB_PASSWORD.get().is_none() {
+            if let Some(argon2_hash) = get_admin_password(&conn).await? {
+                let parsed_hash = PasswordHash::new(&argon2_hash)
+                    .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+                
+                for attempt in 1..=MAX_ATTEMPTS {
+                    if check_password_hash(&parsed_hash).is_ok() {
+                        break;
+                    }
+                    
+                    println!("Wrong password, attempts: {}/{}", attempt, MAX_ATTEMPTS);
+                    if attempt >= MAX_ATTEMPTS {
+                        println!("Maximum password attempts exceeded!!");
+                        std::process::exit(1);
+                    }
+                }
+            }
+        }

300-334: Strong password hashing implementation with Argon2.

The function correctly generates a random salt and uses Argon2 for secure password hashing, following cryptographic best practices.

The comment on line 322 refers to storing "the key and salt securely", but only the key (password hash) is stored. Consider updating the comment for clarity:

-    // Store the key and salt securely (e.g., in a file or database)
+    // Store the password hash securely in the database

959-1057: Good test coverage for encryption and decryption functionality.

The test module properly tests the encryption and decryption of stored strings in an in-memory SQLite database, which is valuable for validating the core functionality of the encryption feature.

Consider these improvements to the test:

  1. Use a constant for the number of test entries rather than hardcoding the value in multiple places
  2. Instead of using unwrap() which would cause panics, propagate errors consistently using the ? operator
  3. Either use a consistent approach for salts (all fixed or all random) or explicitly document why you're testing both approaches
        for i in 0..total_entries {
            let value_string = format!("Entry {}", i % 5);
            println!("Inserting value {}", value_string);
-            let value_string = if i % 3 == 1 {
-                store_encrypted(&value_string, Some(&password), Some(salt))
-                    .await
-                    .unwrap() // Encrypt the string
-                              // Note: id is INTEGER PRIMARY KEY, so it will auto-increment if we don't specify it,
-                              // or we can specify it like this. Let's specify for clarity.
-            } else {
-                store_encrypted(&value_string, Some(&password), None)
-                    .await
-                    .unwrap() // Encrypt the string
-            };
+            // Test both fixed and random salts
+            let salt_option = if i % 3 == 1 { Some(salt) } else { None };
+            let value_string = store_encrypted(&value_string, Some(&password), salt_option)
+                .await
+                .map_err(|e| Error::RowNotFound)?; // Convert the error to sqlx::Error
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2252eb and 24eb134.

📒 Files selected for processing (1)
  • src/db.rs (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (1)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.522Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
🔇 Additional comments (9)
src/db.rs (9)

35-50: Well-implemented file permission restriction with cross-platform handling.

The function properly secures database files with owner-only permissions on Unix systems and provides appropriate messaging for Windows environments where different permission models exist.


122-145: Well-structured password verification with secure handling.

The password verification function properly uses Argon2 for hash verification and securely stores the password in memory using SecretString.


147-203: Comprehensive password setup process with secure memory handling.

The function provides a robust password creation workflow with clear requirements display, strength validation, confirmation matching, and secure storage. The use of zeroize() on line 186 to clear sensitive data from memory is an excellent security practice.


219-222: File permission restriction is correctly applied during database creation.

The implementation ensures that newly created database files have restricted permissions from the start, which is a good security practice.


232-240: Secure admin password handling during database initialization.

The code properly collects and stores the admin password hash during database creation, ensuring that encryption can be set up right from the beginning.


643-663: Clean implementation to fetch admin password from database.

The function properly retrieves the admin password hash from the users table, handling potential errors appropriately.


702-713: User creation updated to include password handling.

The function now correctly handles the admin_password field in the User struct, enabling the encryption feature.


780-812: Secure seller pending order check with encryption support.

The implementation handles both encrypted and unencrypted database scenarios, properly decrypting stored public keys when needed. This maintains security while preserving functionality.

Based on the retrieved learning, the error handling for decrypt_data on line 793 is correct. Previous review comments had suggested removing the map_err(MostroInternalErr) call, but we now understand that this conversion is necessary because decrypt_data from mostro_core::order returns a type that needs to be wrapped.


830-863: Secure buyer pending order check with encryption support.

The implementation mirrors the seller check logic, maintaining consistent error handling and encryption support throughout the codebase.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/db.rs (5)

219-221: Redundant TODO comment on restricting file permissions

The TODO comment about checking if the permission restriction works on Windows is misleading since the code already handles Windows by using cfg attributes.

-        // TODO: check if this is works on windows
         restrict_file_permissions(db_path)
             .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;

235-239: Error handling inconsistency with password operations

The password hash storage error is converted to a DbAccessError which isn't sufficiently specific about the actual operation failing.

Consider using a more specific error variant for password handling operations:

         if let Some(password) = MOSTRO_DB_PASSWORD.get() {
-            store_password_hash(password, &pool).await.map_err(|e| {
-                MostroInternalErr(ServiceError::DbAccessError(e.to_string()))
-            })?;
+            store_password_hash(password, &pool).await?;
         }

This assumes store_password_hash already returns an appropriate MostroError type. If not, consider adding a specific error variant for password operations.


282-291: Improve user feedback for password attempts

The current implementation shows "Wrong password, attempts: X" without indicating how many attempts are allowed.

Make the password attempt feedback more user-friendly:

-                    println!("Wrong password, attempts: {}", attempts);
+                    println!("Wrong password. {} of {} attempts used.", attempts, max_attempts);
                     if attempts >= max_attempts {
-                        println!("Maximum password attempts exceeded!!");
+                        println!("Maximum password attempts exceeded. Please contact the administrator.");
                         std::process::exit(1);
                     }

792-798: Fix typo in "Foundd" comment

There's a typo in the comment which indicates a lack of attention to detail.

            if master_pubkey_decrypted == pubkey {
-                // Foundd another waiting-buyer-invoice order with the same pubkey
+                // Found another waiting-payment order with the same pubkey
                 return Ok(true);
            }

845-848: Fix typo in "Foundd" comment here too

Same typo appears in another comment.

            if master_pubkey_decrypted == pubkey {
-                // Foundd another waiting-buyer-invoice order with the same pubkey
+                // Found another waiting-buyer-invoice order with the same pubkey
                 return Ok(true);
            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24eb134 and 015c2a7.

📒 Files selected for processing (1)
  • src/db.rs (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (1)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.522Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
🔇 Additional comments (2)
src/db.rs (2)

802-812: Query optimization with query_scalar

You're correctly using query_scalar for this database check, which is cleaner than manually extracting values from rows. This is a good practice.


959-1056: 🛠️ Refactor suggestion

Test hardcodes credentials and doesn't clean up properly

The test hardcodes a password and doesn't properly clean up sensitive data after the test.

Apply these practices to improve security in the test:

  1. Use a randomly generated password for testing
  2. Properly zeroize the password after use
  3. Don't expose encryption timing details in logs
-    const SECRET_PASSWORD: &str = "test_password"; // Example password for encryption
+    // Generate a random password for testing
+    fn generate_test_password() -> String {
+        use rand::{distributions::Alphanumeric, Rng};
+        rand::thread_rng()
+            .sample_iter(&Alphanumeric)
+            .take(16)
+            .map(char::from)
+            .collect()
+    }

     #[tokio::test]
     async fn test_fetch_string_column_scalar() -> Result<(), Error> {
         // 1. Setup: Create in-memory DB and table
         let pool = setup_db().await?;
         println!("In-memory database and table created for test.");

         // 2. Populate: Insert 100 entries
         let total_entries = 10;

-        // Use a SecretString for the password
-        let password = SecretString::from(SECRET_PASSWORD);
+        // Use a SecretString with a random password
+        let mut raw_password = generate_test_password();
+        let password = SecretString::from(raw_password.clone());

         println!("Inserting {} entries...", total_entries);

And at the end of the test:

         println!("Test passed!");
+        // Clean up sensitive data
+        raw_password.zeroize();
         Ok(()) // Return Ok to indicate test success
     }

Likely an incorrect or invalid review comment.

Comment thread src/db.rs Outdated
Comment thread src/db.rs
Comment thread src/db.rs

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @arkanoider
When at least one of the two users in an order is in privacy mode, if they open a dispute, the admin attempts to accept it and this happens:

2025-04-30T17:33:25.144229Z  WARN mostrod::app: Error in AdminTakeDispute with context Error in database access: Error caused by Error in database access: no rows returned by a query that expected to return at least one row - inner message Error caused by Error in database access: no rows returned by a query that expected to return at least one row

In the database, the dispute goes to in-progress status, but the new dispute event with the updated status is not published, nor are messages sent to the users or the admin.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/db.rs (6)

214-267: Consider removing database file on password setup failure

If the get_user_password() or store_password_hash() functions fail after the database file is created, the file remains but might be in an inconsistent state. Consider extending the error handling to clean up the file in these cases as well.

 match sqlx::migrate!().run(&pool).await {
     Ok(_) => {
         tracing::info!(
             "Successfully created database file at {}",
             db_path.display(),
         );
-        // Get user password
-        get_user_password().await?;
-        // Save admin password hash securely
-        if let Some(password) = MOSTRO_DB_PASSWORD.get() {
-            store_password_hash(password, &pool).await.map_err(|e| {
-                MostroInternalErr(ServiceError::DbAccessError(e.to_string()))
-            })?;
-        }
-        pool
+        // Get user password
+        match get_user_password().await {
+            Ok(_) => {
+                // Save admin password hash securely
+                if let Some(password) = MOSTRO_DB_PASSWORD.get() {
+                    match store_password_hash(password, &pool).await {
+                        Ok(_) => pool,
+                        Err(e) => {
+                            if let Err(cleanup_err) = std::fs::remove_file(db_path) {
+                                tracing::error!(
+                                    error = %cleanup_err,
+                                    path = %db_path.display(),
+                                    "Failed to clean up database file after password storage failure"
+                                );
+                            }
+                            return Err(MostroInternalErr(ServiceError::DbAccessError(e.to_string())))
+                        }
+                    }
+                } else {
+                    pool
+                }
+            },
+            Err(e) => {
+                if let Err(cleanup_err) = std::fs::remove_file(db_path) {
+                    tracing::error!(
+                        error = %cleanup_err,
+                        path = %db_path.display(),
+                        "Failed to clean up database file after password setup failure"
+                    );
+                }
+                return Err(e)
+            }
+        }
     }
     Err(e) => {

780-813: Add caching for decrypted pubkeys in seller_has_pending_order

The function decrypts potentially many public keys in a loop, which could be inefficient. Consider implementing a cache to avoid repeated decryption of the same key, especially in cases where a seller might have multiple pending orders.

 // Check if database is encrypted
 if MOSTRO_DB_PASSWORD.get().is_some() {
+    // Cache for decrypted pubkeys to avoid repeated decryption
+    let mut decrypted_cache = std::collections::HashMap::new();
+    
     let orders_to_check: Vec<String> = sqlx::query_scalar(
         r#"
             SELECT master_seller_pubkey FROM orders WHERE status = 'waiting-payment'
         "#,
     )
     .fetch_all(&mut conn)
     .await
     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
     // search for a waiting-buyer-invoice order with the same pubkey
     for master_key in orders_to_check {
-        // Decrypt master buyer pubkey
-        let master_pubkey_decrypted =
-            decrypt_data(master_key, MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
+        // Check cache first, decrypt only if not found
+        let master_pubkey_decrypted = if let Some(decrypted) = decrypted_cache.get(&master_key) {
+            decrypted.clone()
+        } else {
+            let decrypted = decrypt_data(master_key.clone(), MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
+            decrypted_cache.insert(master_key, decrypted.clone());
+            decrypted
+        };
         if master_pubkey_decrypted == pubkey {
             // Foundd another waiting-buyer-invoice order with the same pubkey
             return Ok(true);

831-864: Add caching for decrypted pubkeys in buyer_has_pending_order

Similar to the seller function, this function decrypts potentially many public keys in a loop. Implementing a cache would improve performance.

 // Check if database is encrypted
 if MOSTRO_DB_PASSWORD.get().is_some() {
+    // Cache for decrypted pubkeys to avoid repeated decryption
+    let mut decrypted_cache = std::collections::HashMap::new();
+    
     let orders_to_check: Vec<String> = sqlx::query_scalar(
         r#"
             SELECT master_buyer_pubkey FROM orders WHERE status = 'waiting-buyer-invoice'
         "#,
     )
     .fetch_all(&mut conn)
     .await
     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
     // search for a waiting-buyer-invoice order with the same pubkey
     for master_key in orders_to_check {
-        // Decrypt master buyer pubkey
-        let master_pubkey_decrypted =
-            decrypt_data(master_key, MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
+        // Check cache first, decrypt only if not found
+        let master_pubkey_decrypted = if let Some(decrypted) = decrypted_cache.get(&master_key) {
+            decrypted.clone()
+        } else {
+            let decrypted = decrypt_data(master_key.clone(), MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
+            decrypted_cache.insert(master_key, decrypted.clone());
+            decrypted
+        };
         if master_pubkey_decrypted == pubkey {
             // Foundd another waiting-buyer-invoice order with the same pubkey
             return Ok(true);

795-798: Fix typo in comment

There's a typo in both seller_has_pending_order and buyer_has_pending_order functions: "Foundd" should be "Found".

 if master_pubkey_decrypted == pubkey {
-    // Foundd another waiting-buyer-invoice order with the same pubkey
+    // Found another waiting-buyer-invoice order with the same pubkey
     return Ok(true);
 }

Also applies to: 846-849


959-1057: Test function looks good, but consider adding more comprehensive tests

The test for encryption/decryption functionality is well-implemented. Consider adding tests for other scenarios:

  1. Empty password handling
  2. Password validation
  3. Testing password attempt limits
  4. Performance comparison with and without caching

960-998: Refactor test to use constants for predictable test cases

For more predictable test results and clearer assertions, refactor the test to use predefined test data instead of generating entries in a loop with varying conditions.

 #[tokio::test]
 async fn test_fetch_string_column_scalar() -> Result<(), Error> {
     // 1. Setup: Create in-memory DB and table
     let pool = setup_db().await?;
     println!("In-memory database and table created for test.");

-    // 2. Populate: Insert 100 entries
-    let total_entries = 10;
+    // 2. Populate: Insert test entries with known values
+    let test_entries = [
+        "Test Entry 1",
+        "Test Entry 2",
+        "Test Entry 3",
+        "Test Entry 4",
+        "Test Entry 5",
+    ];

     // Use a SecretString for the password
     let password = SecretString::from(SECRET_PASSWORD);

-    println!("Inserting {} entries...", total_entries);
-    for i in 0..total_entries {
-        let value_string = format!("Entry {}", i % 5);
-        println!("Inserting value {}", value_string);
-        let value_string = if i % 3 == 1 {
-            store_encrypted(&value_string, Some(&password))
-                .await
-                .unwrap() // Encrypt the string
-                          // Note: id is INTEGER PRIMARY KEY, so it will auto-increment if we don't specify it,
-                          // or we can specify it like this. Let's specify for clarity.
-        } else {
-            store_encrypted(&value_string, Some(&password))
-                .await
-                .unwrap() // Encrypt the string
-        };
+    println!("Inserting {} entries...", test_entries.len());
+    for (i, entry) in test_entries.iter().enumerate() {
+        println!("Inserting value {}", entry);
+        let encrypted_string = store_encrypted(entry, Some(&password))
+            .await
+            .unwrap(); // Encrypt the string
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 090666c and 7a14b4d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml (2 hunks)
  • src/app/admin_take_dispute.rs (2 hunks)
  • src/app/release.rs (5 hunks)
  • src/db.rs (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Cargo.toml
  • src/app/admin_take_dispute.rs
  • src/app/release.rs
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (1)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
🔇 Additional comments (3)
src/db.rs (3)

35-50: LGTM: Secure file permission implementation

Good implementation of platform-specific file permissions. The code correctly sets owner-only read/write permissions (0o600) on Unix systems and provides a warning for Windows where the equivalent functionality isn't implemented.


52-120: Password validation is well implemented

The password requirements implementation is thorough and follows security best practices with appropriate validation checks for length, complexity, and character variety.


274-294: 🛠️ Refactor suggestion

Handle password attempt counting properly

The code correctly implements a maximum number of password attempts but has a potential issue. If a password is correct but MOSTRO_DB_PASSWORD.set() fails, it will still count as a failed attempt. Consider restructuring to separate validation from storage failures.

 if MOSTRO_DB_PASSWORD.get().is_none() {
     while let Some(argon2_hash) = get_admin_password(&conn).await? {
         // Database already exists - and yet opened
         let parsed_hash = PasswordHash::new(&argon2_hash)
             .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
-        if check_password_hash(&parsed_hash).is_ok() {
+        // Verify password without storing it first
+        print!("Enter database password (attempt {}/{}): ", attempts + 1, max_attempts);
+        std::io::stdout().flush().unwrap();
+        let password = read_password()
+            .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+            
+        if Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok() {
+            // Password is correct, now try to store it
+            if MOSTRO_DB_PASSWORD.set(SecretString::from(password)).is_ok() {
+                break;
+            } else {
+                return Err(MostroInternalErr(ServiceError::DbAccessError(
+                    "Failed to save password".to_string(),
+                )));
+            }
             break;
         } else {
             attempts += 1;

Likely an incorrect or invalid review comment.

Comment thread src/db.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/db.rs (2)

53-120: Password strength validation is well-implemented but has a small optimization opportunity.

The password requirements implementation is thorough with clear validation messages. The intentional handling of empty passwords (line 101-103) aligns with the project's approach of making encryption optional.

In the is_strong_password method, you're calling validate twice unnecessarily:

fn is_strong_password(&self, password: &str) -> bool {
-    match self.validate(password).is_empty() {
+    let failures = self.validate(password);
+    match failures.is_empty() {
        true => true,
        false => {
            println!("\nPassword is not strong enough:");
-            for failure in self.validate(password) {
+            for failure in failures {
                println!("- {}", failure);
            }
            false
        }
    }
}

779-813: Encryption-aware pending order check with proper error handling.

The implementation correctly handles both encrypted and non-encrypted database scenarios. For encrypted databases, it fetches all relevant orders and decrypts each key for comparison.

I notice a small typo in line 796: "Foundd" should be "Found", but this is just a comment and doesn't affect functionality.

-            // Foundd another waiting-buyer-invoice order with the same pubkey
+            // Found another waiting-buyer-invoice order with the same pubkey
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ee56b4 and 6f0f060.

📒 Files selected for processing (1)
  • src/db.rs (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
🔇 Additional comments (10)
src/db.rs (10)

36-50: Good security implementation with platform-specific behavior.

The function properly restricts database file permissions to owner-only read/write (0o600) on Unix systems, with an appropriate placeholder message for Windows. This helps protect the database from unauthorized access by other users on the same system.


122-145: Properly implemented password verification with security focus.

The password verification implementation correctly uses Argon2 for secure password hashing and saves the password securely using SecretString to prevent memory exposure.

I note you've implemented the 3-attempt limit with application exit elsewhere in the code, which is an excellent security practice to prevent brute force attacks.


147-203: Well-structured password creation flow with appropriate security prompts.

The password creation interface provides clear requirements to users, handles empty passwords gracefully, and securely stores the credentials. The use of zeroize() on line 186 is an excellent security practice to clear sensitive data from memory.


213-266: Good database initialization with security considerations.

The database file creation properly restricts permissions and implements the password setup flow. The error handling is comprehensive and includes proper cleanup on failure.


267-296: Strong password verification flow with brute-force protection.

The implementation properly handles existing database access by verifying the password against the stored hash. The 3-attempt limit with application exit on line 288-290 is an excellent security measure against brute force attacks.


301-334: Strong password hashing using industry-standard Argon2.

The implementation correctly uses Argon2 with a random salt for secure password hashing. The hash is properly stored in the user database entry.


643-663: Admin password retrieval function looks good.

The function correctly retrieves the admin password from the users table with appropriate error handling.


702-729: User creation function properly extended to handle admin password.

The SQL query has been updated to include the admin_password field, with appropriate binding in the query parameters.


830-863: Encryption-aware buyer pending order check works correctly.

Similar to the seller check, this implementation correctly handles both encrypted and non-encrypted databases. The error handling is consistent and appropriate.

Same typo in line 846:

-            // Foundd another waiting-buyer-invoice order with the same pubkey
+            // Found another waiting-buyer-invoice order with the same pubkey

959-1056: Well-structured test for encryption/decryption functionality.

The test correctly sets up an in-memory database, inserts encrypted entries, retrieves and decrypts them. The timing information logged is valuable for performance assessment during testing.

@arkanoider arkanoider requested a review from Catrya May 6, 2025 21:04

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

tACK

@Catrya

Catrya commented May 7, 2025

Copy link
Copy Markdown
Member

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

@arkanoider

Copy link
Copy Markdown
Collaborator Author

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

Hi @Catrya , i was able to reproduce it....seems that for a reason I cannot understand CLI didn't send in that case the next_trade_keys on release so the order is not saved and you have the error in db search.
Anyway I am fixing some things on mostro-core and mostro. So please do a round of test again on your side.

@Catrya

Catrya commented May 7, 2025

Copy link
Copy Markdown
Member

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

Hi @Catrya , i was able to reproduce it....seems that for a reason I cannot understand CLI didn't send in that case the next_trade_keys on release so the order is not saved and you have the error in db search. Anyway I am fixing some things on mostro-core and mostro. So please do a round of test again on your side.

Okay, but that bug I mentioned isn't related to this PR; it's older. What do you think about fixing that bug in a separate PR?

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

I was referring to that here in this PR says that there are branch conflicts that prevent this PR from being merged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/db.rs (2)

797-815: Fix typo in comment

There's a small typo in the comment.

-            // Foundd another waiting-buyer-invoice order with the same pubkey
+            // Found another waiting-buyer-invoice order with the same pubkey

863-864: Fix typo in comment

Same typo appears here.

-            // Foundd another waiting-buyer-invoice order with the same pubkey
+            // Found another waiting-buyer-invoice order with the same pubkey
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3a676a and 23a773c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • Cargo.toml (1 hunks)
  • src/app/take_buy.rs (2 hunks)
  • src/app/take_sell.rs (2 hunks)
  • src/db.rs (9 hunks)
  • src/util.rs (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/take_buy.rs
  • src/app/take_sell.rs
  • src/util.rs
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
🔇 Additional comments (5)
src/db.rs (5)

35-50: Good platform-specific permission handling

The implementation correctly restricts file permissions on Unix systems using 0o600 (owner read/write only) which is appropriate for sensitive files like encrypted databases. The code also properly handles Windows with a clear warning message.


52-120: Password strength validation is well-implemented

The PasswordRequirements struct provides a robust set of validation rules with clear error messages. The default requirements (12+ chars, mixed case, numbers, and special characters) follow modern security best practices.


122-153: Security best practice: Anti-timing attack measures

The random delay implementation in password verification is an excellent security measure that helps mitigate timing attacks. The combination of consistent delay (100ms) plus random component (0-999ms) provides good protection while remaining responsive.


155-220: Robust password creation flow with secure handling

The password creation flow follows security best practices including:

  • Clear requirements communication
  • Confirmation input
  • Secure input via read_password
  • Strong validation
  • Immediate memory cleanup with zeroize
  • Secure storage using SecretString

976-1073: Well-structured test for encryption functionality

The test module provides good coverage of the core encryption/decryption functionality, using an in-memory SQLite database for isolation. The performance measurement is helpful for understanding the real-world impact of the encryption layer.

Comment thread src/db.rs
@arkanoider

Copy link
Copy Markdown
Collaborator Author

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

Hi @Catrya , i was able to reproduce it....seems that for a reason I cannot understand CLI didn't send in that case the next_trade_keys on release so the order is not saved and you have the error in db search. Anyway I am fixing some things on mostro-core and mostro. So please do a round of test again on your side.

Okay, but that bug I mentioned isn't related to this PR; it's older. What do you think about fixing that bug in a separate PR?

@arkanoider I hadn't noticed the conflicts, I'm sure you had, but just in case, I remind you to keep an eye on it :)

I was referring to that here in this PR says that there are branch conflicts that prevent this PR from being merged.

Yes I will rebase this branch on main when we close smaller prs. Will take care of that. Yes I agree that is better to split that possible mostro-cli issue in another PR when the cause it's clear.

@arkanoider arkanoider requested a review from Catrya May 7, 2025 22:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/db.rs (2)

805-837: 🛠️ Refactor suggestion

Apply the same caching mechanism here for consistent implementation.

Similar to the seller pending order check, the buyer pending order check would benefit from the same caching mechanism to improve performance with large numbers of orders.


759-791: 🛠️ Refactor suggestion

Consider caching decrypted keys for better performance.

The approach of decrypting all master seller keys for every pending order check could become performance-intensive as the database grows. Consider implementing a caching mechanism for recently decrypted keys to reduce repeated computations.

+ // Add this at the module level with appropriate mutex protection
+ use std::collections::HashMap;
+ use std::sync::Mutex;
+ lazy_static! {
+     static ref DECRYPTED_KEYS_CACHE: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
+ }
+
  // Inside the function where decryption happens
  if MOSTRO_DB_PASSWORD.get().is_some() {
      let orders_to_check: Vec<String> = sqlx::query_scalar(...)
          .fetch_all(pool)
          .await
          .map_err(...)?;
      
      for master_key in orders_to_check {
+         // Check cache first
+         let master_pubkey_decrypted = {
+             let mut cache = DECRYPTED_KEYS_CACHE.lock().unwrap();
+             if let Some(cached) = cache.get(&master_key) {
+                 cached.clone()
+             } else {
+                 let decrypted = decrypt_data(master_key.clone(), MOSTRO_DB_PASSWORD.get())
+                     .map_err(MostroInternalErr)?;
+                 // Cache with reasonable size limit
+                 if cache.len() > 1000 {
+                     // Simple eviction policy - clear half the cache when full
+                     let keys_to_remove: Vec<String> = cache.keys()
+                         .take(cache.len() / 2)
+                         .cloned()
+                         .collect();
+                     for key in keys_to_remove {
+                         cache.remove(&key);
+                     }
+                 }
+                 cache.insert(master_key.clone(), decrypted.clone());
+                 decrypted
+             }
+         };
-         // Decrypt master seller pubkey
-         let master_pubkey_decrypted =
-             decrypt_data(master_key, MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
          if master_pubkey_decrypted == pubkey {
              return Ok(true);
          }
      }
  }
🧹 Nitpick comments (3)
src/db.rs (3)

52-106: Clarify the intentional bypass of validation for empty passwords.

The password validation logic includes a special case that clears all validation failures if the password is empty. While this might be intentional to allow skipping encryption, it should be explicitly documented for clarity.

 // If password is empty, clear failures
 if password.is_empty() {
+    // Empty password is allowed to support optional encryption
     failures.clear();
 }

933-1030: Add more assertions to strengthen the test.

The encryption/decryption test is well-structured but could benefit from more explicit assertions to verify the expected behavior beyond just checking for success.

 // At line 1021
 if hash_set_values.contains("Entry 0") {
     println!("Entry 0 found in the hash set.");
 } else {
     println!("Entry 0 not found in the hash set.");
 }
+// Add explicit assertions to verify expected behavior
+assert!(hash_set_values.contains("Entry 0"), "Should contain Entry 0");
+assert!(hash_set_values.contains("Entry 1"), "Should contain Entry 1");
+assert!(hash_set_values.contains("Entry 2"), "Should contain Entry 2");
+assert!(hash_set_values.contains("Entry 3"), "Should contain Entry 3");
+assert!(hash_set_values.contains("Entry 4"), "Should contain Entry 4");
+assert_eq!(hash_set_values.len(), 5, "Should have exactly 5 unique entries");

994-998: Consider batching database insertions for better performance in tests.

For large test datasets, using a batch insert approach could significantly improve test execution time.

-// Individual inserts
-for i in 0..total_entries {
-    let value_string = format!("Entry {}", i % 5);
-    println!("Inserting value {}", value_string);
-    let value_string = if i % 3 == 1 {
-        store_encrypted(&value_string, Some(&password))
-            .await
-            .unwrap()
-    } else {
-        store_encrypted(&value_string, Some(&password))
-            .await
-            .unwrap()
-    };
-    sqlx::query("INSERT INTO items (id, value) VALUES (?, ?)")
-        .bind(i as i64)
-        .bind(&value_string)
-        .execute(&pool)
-        .await?;
-}
+// Prepare batch values
+let mut query_builder = String::from("INSERT INTO items (id, value) VALUES ");
+let mut params = Vec::new();
+
+for i in 0..total_entries {
+    let value_string = format!("Entry {}", i % 5);
+    println!("Inserting value {}", value_string);
+    let encrypted_value = store_encrypted(&value_string, Some(&password))
+        .await
+        .unwrap();
+    
+    if i > 0 {
+        query_builder.push_str(", ");
+    }
+    query_builder.push_str(&format!("({}, ?)", i));
+    params.push(encrypted_value);
+}
+
+// Execute batch insert
+let mut query = sqlx::query(&query_builder);
+for param in params {
+    query = query.bind(param);
+}
+query.execute(&pool).await?;

Note: This is a performance optimization suggestion for the test code, but may not be necessary for the small number of entries (10) currently used.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23a773c and ff26865.

📒 Files selected for processing (1)
  • src/db.rs (17 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
🔇 Additional comments (5)
src/db.rs (5)

35-50: Good implementation of platform-specific file permission restrictions.

The restrict_file_permissions function properly handles platform differences with conditional compilation for Unix vs Windows. Setting permissions to 0o600 (owner read/write only) is a good security measure for protecting sensitive database files.


127-135: Good security practice with randomized delay.

Adding a small, randomized delay before password verification is an excellent security practice that helps mitigate timing attacks by obscuring the actual processing time of password validation.


294-310: Good implementation of password attempt limiting.

The code limits password attempts to 3 tries, which is a solid security practice to prevent brute force attacks. Exiting the application after the limit is reached is an appropriate action for a sensitive database operation.


827-837: Good use of query_scalar for boolean existence check.

Using query_scalar::<_, bool> directly when the database isn't encrypted is an efficient approach that avoids unnecessary type conversions from i64 to bool.


631-651: Well-structured admin password retrieval function.

The get_admin_password function is cleanly implemented to fetch the admin's password hash from the database with proper error handling and optional result handling.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/db.rs (2)

52-121: Robust password validation implementation with room for minor improvement.

The password requirements implementation is thorough and provides good security validation. The empty password handling with the comment on line 102 clarifies the intent.

Consider implementing a strength score calculation method rather than just binary validation, which would allow for more nuanced feedback to users based on password complexity.


759-793: Secure seller pending order check with encrypted key support.

The implementation properly handles both encrypted and unencrypted database states, providing a fallback mechanism for unencrypted databases.

There's a typo in the comment on line 775: "Foundd" should be "Found".

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff26865 and dcbe410.

📒 Files selected for processing (1)
  • src/db.rs (17 hunks)
🔇 Additional comments (9)
src/db.rs (9)

24-50: Good security implementation of file permission restrictions.

The implementation of Unix-specific file permission restrictions is well thought out. It's great to see a proper cross-platform approach with conditionally compiled code for Unix and Windows systems.


123-154: Good security practice with randomized delays for password verification.

Including randomized delays between 100-1100ms during password verification is an excellent security practice to mitigate timing attacks. The error handling is also robust.


156-221: Well-implemented secure password input flow.

The user password input flow is well-designed with:

  • Clear password requirements presentation
  • Secure password reading
  • Confirmation mechanism
  • Proper memory cleanup with zeroize
  • Support for optional encryption (empty password)

230-314: Comprehensive database initialization with security checks.

The database connection flow properly handles both new and existing databases with appropriate security measures. The 3-attempt limit for password entry is a good security practice.


319-352: Secure password hashing implementation.

The implementation uses industry-standard Argon2 for password hashing with random salt generation, which is a strong security choice. The admin user creation is also handled properly.


354-530: Good refactoring of database operations.

Several database functions have been refactored to execute queries directly on the connection pool rather than acquiring individual connections first, which is a good performance practice.


632-652: Clean admin password retrieval implementation.

The function properly retrieves the admin password hash from the database with appropriate error handling.


805-838: Secure buyer pending order check with encrypted key support.

Similar to the seller pending order check, this implementation handles both encrypted and unencrypted database states appropriately.


934-1031: Comprehensive encryption testing with benchmarking.

The test module provides good validation of the encryption/decryption functionality with appropriate benchmarking of performance. Using an in-memory SQLite database for tests is an excellent approach.

@MostroP2P MostroP2P deleted a comment from coderabbitai Bot May 8, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/db.rs (4)

228-255: Consider handling database creation failure more gracefully

When setting up a new database, the code currently exits the application if password setup fails. Consider handling this more gracefully to prevent data loss.

  // Get user password
- get_user_password().await?;
+ match get_user_password().await {
+     Ok(_) => {}
+     Err(e) => {
+         tracing::error!("Failed to set up database password: {}", e);
+         println!("Failed to set up database password. Continuing without encryption.");
+     }
+ }
  
  // Save admin password hash securely
  if let Some(password) = MOSTRO_DB_PASSWORD.get() {
      store_password_hash(password, &pool).await.map_err(|e| {
          MostroInternalErr(ServiceError::DbAccessError(e.to_string()))
      })?;
  }

282-311: Three-attempt password verification with forced exit

The implementation of a three-attempt limit with application exit on failure is a strong security practice. This prevents brute force attacks by terminating the program after three failures.

Consider logging these failed attempts to help identify potential attacks.

  println!("Wrong password, attempts: {}", attempts);
+ tracing::warn!("Failed database password attempt {}/{}", attempts, max_attempts);
  if attempts >= max_attempts {
      println!("Maximum password attempts exceeded!!");
+     tracing::error!("Maximum password attempts exceeded, terminating application");
      std::process::exit(1);
  }

797-841: Duplicate implementation pattern in buyer and seller functions

The pattern for checking encrypted/unencrypted databases is identical in both seller_has_pending_order and buyer_has_pending_order, with only the field and status names changed.

Consider extracting this pattern into a helper function to reduce code duplication:

async fn has_pending_order_with_status(
    pool: &SqlitePool,
    pubkey: String,
    master_key_field: &str,
    status: &str
) -> Result<bool, MostroError> {
    // Validate public key format (32-bytes hex)
    if !pubkey.chars().all(|c| c.is_ascii_hexdigit()) || pubkey.len() != 64 {
        return Err(MostroCantDo(CantDoReason::InvalidPubkey));
    }

    // Check if database is encrypted
    if MOSTRO_DB_PASSWORD.get().is_some() {
        let orders_to_check: Vec<String> = sqlx::query_scalar(
            &format!("SELECT {} FROM orders WHERE status = ?", master_key_field)
        )
        .bind(status)
        .fetch_all(pool)
        .await
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
        
        // search for orders with the same pubkey
        for master_key in orders_to_check {
            // Decrypt master pubkey
            let master_pubkey_decrypted =
                decrypt_data(master_key, MOSTRO_DB_PASSWORD.get()).map_err(MostroInternalErr)?;
            if master_pubkey_decrypted == pubkey {
                return Ok(true);
            }
        }
        Ok(false)
    }
    // if not encrypted, use the default search
    else {
        let exists = sqlx::query_scalar::<_, bool>(
            &format!("SELECT EXISTS (SELECT 1 FROM orders WHERE {} = ? AND status = ?)", master_key_field)
        )
        .bind(pubkey)
        .bind(status)
        .fetch_one(pool)
        .await
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
        Ok(exists)
    }
}

Then call it with the appropriate parameters:

pub async fn buyer_has_pending_order(pool: &SqlitePool, pubkey: String) -> Result<bool, MostroError> {
    has_pending_order_with_status(pool, pubkey, "master_buyer_pubkey", "waiting-buyer-invoice").await
}

pub async fn seller_has_pending_order(pool: &SqlitePool, pubkey: String) -> Result<bool, MostroError> {
    has_pending_order_with_status(pool, pubkey, "master_seller_pubkey", "waiting-payment").await
}

936-1062: Comprehensive encryption test with performance tracking

The test module thoroughly verifies the encryption/decryption functionality:

  • Creates in-memory test database
  • Inserts multiple encrypted values
  • Retrieves and decrypts them
  • Verifies correctness with assertions
  • Measures decryption performance

The performance tracking is valuable for understanding the real-world impact of encryption on the application.

One minor concern: The test uses a deterministic salt pattern similar to the main code. Consider using random salts in the test as well.

- let salt_base = b"1H/aaYsf8&asduA";
- for i in 0..total_entries {
-     let salt = format!("{}{}", String::from_utf8_lossy(salt_base), i % 5);
-     salt_vec.push(SaltString::encode_b64(salt.as_bytes()).unwrap());
- }
+ // Generate random salts for testing
+ for _ in 0..total_entries {
+     salt_vec.push(SaltString::generate(&mut rand_core::OsRng));
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcbe410 and 0a4916c.

📒 Files selected for processing (4)
  • src/app/take_buy.rs (2 hunks)
  • src/app/take_sell.rs (2 hunks)
  • src/db.rs (17 hunks)
  • src/util.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/take_buy.rs
  • src/app/take_sell.rs
  • src/util.rs
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (3)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:0-0
Timestamp: 2025-05-08T13:07:29.455Z
Learning: For the database encryption in Mostro, the developer prefers caching only derived encryption keys rather than caching decrypted master keys in RAM, as a security measure to reduce the attack surface from potential memory dumps or runtime vulnerabilities.
🧬 Code Graph Analysis (1)
src/db.rs (2)
src/cli/settings.rs (2)
  • new (127-154)
  • get_db (164-166)
src/util.rs (1)
  • get_keys (466-476)
🔇 Additional comments (8)
src/db.rs (8)

1-31: Import choices align well with the encryption strategy

The imports reflect a well-thought-out approach to security:

  • argon2 for password hashing (industry-standard KDF)
  • secrecy for zero-knowledge memory management
  • rpassword for secure terminal input
  • Platform-specific imports for proper file permission handling

Good security practice using multiple specialized crates rather than trying to implement security primitives in-house.


32-47: Well-implemented cross-platform file permissions

Good implementation of file permission restrictions with platform-specific handling:

  • Unix: Sets 0o600 permissions (owner read/write only)
  • Windows: Provides a clear user notice

The error handling is appropriate, mapping OS errors to the application's error type.


49-118: Strong password validation with clear user feedback

The PasswordRequirements implementation enforces industry-standard security practices:

  • 12+ character length
  • Mix of upper/lowercase, numbers, and special characters
  • Clear error messages for each requirement

The special handling for empty passwords (lines 97-101) makes encryption optional, which aligns with the PR objectives.


120-151: Effective mitigation against timing attacks

The password verification function includes several security best practices:

  • Random delay (100-1100ms) to mitigate timing attacks
  • Secure password reading that doesn't echo to screen
  • No password caching in logs

The random delay implementation is an excellent defense against brute force attacks by making automated attempts significantly slower.


153-218: User-friendly password creation with strong security

This password creation flow balances security with usability:

  • Clear requirements explanation upfront
  • Option to skip encryption entirely
  • Confirmation step to prevent typos
  • Secure memory handling with zeroize() for the confirmation password
  • Storage in a SecretString to prevent memory dumps exposing the password

The loop structure ensures users meet requirements before proceeding.


693-720: Good schema update handling for admin passwords

The add_new_user function has been appropriately updated to include the admin_password field in the SQL query. The updated code accounts for the database schema changes by:

  1. Including admin_password in the column list
  2. Adding the corresponding parameter binding
  3. Including a clear comment about returning the unencrypted public key

751-795: Good implementation of conditional encryption checking

The seller_has_pending_order function smartly handles both encrypted and unencrypted databases:

  1. First checks if encryption is enabled via MOSTRO_DB_PASSWORD.get().is_some()
  2. If encrypted, fetches and decrypts all relevant keys individually
  3. Otherwise falls back to direct SQL checking with EXISTS clause

The approach balances security with performance while maintaining backward compatibility.


754-755:

Details

❓ Verification inconclusive

Add input validation for inactive pubkeys

While the function validates pubkey format, it doesn't check if the key is for an active user.


🏁 Script executed:

#!/bin/bash
# Check if there's a related validation in the codebase

# Search for banning or user status checks
rg "is_banned" --type rust

Length of output: 269


I’d like to inspect the function body around that pubkey: String parameter to see where and how it’s used before recommending adding the is_banned check.


🏁 Script executed:

#!/bin/bash
set -e
# Show 5 lines before and after the pubkey parameter in src/db.rs
rg -n "pubkey: String" -C5 src/db.rs

Length of output: 946


I’d like to see how the codebase currently retrieves or checks users by pubkey—this will help determine where to hook in the is_banned check:


🏁 Script executed:

#!/bin/bash
set -e
# Find any SELECTs against the users table
rg -n "SELECT .*FROM users" -t rust -C3

# Find any async fns handling users by pubkey
rg -n "pub async fn .*user.*pubkey" -t rust -C3

Length of output: 50


I want to locate any existing user‐lookup by pubkey to see where to enforce is_banned—let’s search for SELECT/WHERE clauses and binds around pubkey:


🏁 Script executed:

#!/bin/bash
set -e

# Search for any SELECTs against users
rg -n -i "select .*from users" -C3 src

# Look for WHERE clauses on pubkey
rg -n -i "where pubkey" -C3 src

# Find any .bind(pubkey) calls
rg -n "bind(pubkey" -C3 src

Length of output: 49


Verify ban status in order checks

The seller_has_pending_order and buyer_has_pending_order functions only validate the pubkey format and don’t appear to fetch the user record or check is_banned. Please ensure you:

  • Retrieve the user from the users table by pubkey.
  • Enforce is_banned == false before allowing any order‐related logic.

If this check happens upstream, confirm banned users can’t reach these calls.

Comment thread src/db.rs Outdated
Comment thread src/db.rs

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @arkanoider the database is now called mostro.dbmostro.db , it should be called mostro.db

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/db.rs (2)

322-338: Simplify salt generation since only the first salt is used

You're generating 50 salts but only using the first one. If deterministic salt generation is intentional for testing purposes, you can simplify this to just generate the one needed salt.

- // Generate a random salt
- let salt_base = b"1H/aaYsf8&asduA";
- let mut salt_vec: Vec<SaltString> = Vec::new();
- for i in 0..50 {
-     let salt = format!("{}{}", String::from_utf8_lossy(salt_base), i % 5);
-     salt_vec.push(SaltString::encode_b64(salt.as_bytes()).unwrap());
- }
- 
- // Configure Argon2 parameters
- let argon2 = Argon2::default();
- 
- // Derive the key
- let key = argon2
-     .hash_password(password.expose_secret().as_bytes(), &salt_vec[0])
-     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?
-     .to_string();
+ // Generate a deterministic salt for testing
+ let salt_base = b"1H/aaYsf8&asduA";
+ let salt = format!("{}{}", String::from_utf8_lossy(salt_base), 0);
+ let salt_string = SaltString::encode_b64(salt.as_bytes())
+     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+ 
+ // Configure Argon2 parameters
+ let argon2 = Argon2::default();
+ 
+ // Derive the key
+ let key = argon2
+     .hash_password(password.expose_secret().as_bytes(), &salt_string)
+     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?
+     .to_string();

768-815: Consider optimizing encrypted key searches with indexes

The current implementation decrypts all keys matching a status to find a match, which could become inefficient as the database grows. While your approach of caching derived keys helps with decryption speed, you could further optimize query performance.

Add indexes on the status column to improve query performance:

CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);

Additionally, consider adding a comment explaining the performance characteristics and why this approach was chosen over alternatives, to help future maintainers understand the design decisions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 507e984 and 8008197.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • src/db.rs (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (4)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:0-0
Timestamp: 2025-05-08T13:07:29.484Z
Learning: For the database encryption in Mostro, the developer prefers caching only derived encryption keys rather than caching decrypted master keys in RAM, as a security measure to reduce the attack surface from potential memory dumps or runtime vulnerabilities.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:315-354
Timestamp: 2025-05-08T18:10:03.818Z
Learning: In the Mostro codebase, deterministic salt generation is used intentionally in testing functions to benchmark decryption timing and test caching mechanisms. This is a valid testing approach when consistent, reproducible results are needed for performance evaluation.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests
🔇 Additional comments (5)
src/db.rs (5)

37-106: LGTM! Solid password requirements implementation

The password validation is comprehensive with good defaults (12+ chars, mixed case, numbers, special chars). The special handling for empty passwords makes sense for optional encryption.


108-139: Great security implementation for password verification

Good use of Argon2 for password verification with added random delays to mitigate timing attacks. The error messages are appropriately generic.


913-1039: Good test coverage for encryption/decryption

The test provides thorough coverage of the encryption and decryption functionality with performance measurement, using an in-memory database for isolation.


636-656: Clean implementation of the admin password retrieval

Good error handling and clean implementation for retrieving the admin password from the database.


792-796: Correct error handling for decrypt_data

The implementation correctly maps the error from decrypt_data using map_err(MostroInternalErr) as needed for the error type conversion.

Comment thread src/db.rs
Comment on lines +20 to +35
fn restrict_file_permissions(path: &Path) -> Result<(), MostroError> {
#[cfg(unix)]
{
let perms = Permissions::from_mode(0o600);
set_permissions(path, perms)
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
}

#[cfg(windows)]
{
// Optional: could integrate with `winapi` or use a placeholder
println!("⚠️ Skipping permission change on Windows. Set it manually if needed.");
}

Ok(())
}

@coderabbitai coderabbitai Bot May 19, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Windows file permission handling is incomplete

While the Unix implementation correctly restricts file permissions to owner-only (0o600), the Windows implementation only prints a warning without actually securing the file.

Consider implementing proper Windows file security using the windows-acl crate:

#[cfg(windows)]
{
    use windows_acl::acl::{AclEntry, AccessMode};
    use windows_acl::helper;
    
    // Get current user SID
    let current_user = helper::get_current_username()
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
    
    // Create ACL allowing full access to current user only
    let entries = vec![
        AclEntry {
            application_type: Some(AccessMode::Allow),
            trustee: current_user,
            permissions: windows_acl::helper::Permissions::FULL,
        }
    ];
    
    // Apply ACL to database file
    windows_acl::acl::apply_to_file(path.to_str().unwrap(), entries)
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
}

Also, add a docstring to describe the function's purpose and behavior on different platforms.

🤖 Prompt for AI Agents
In src/db.rs around lines 20 to 35, the Windows implementation of
restrict_file_permissions only prints a warning and does not actually restrict
file permissions. Replace the placeholder code with proper Windows ACL handling
using the windows-acl crate: retrieve the current user SID, create an ACL entry
granting full access to that user only, and apply this ACL to the file path.
Also, add a docstring to the function explaining its purpose and
platform-specific behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes @coderabbitai! I don't love windows sorry...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@arkanoider No worries! Many developers share that sentiment 😄

If you'd prefer a simpler approach without adding the windows-acl dependency, you could at least warn Windows users more explicitly in the logs about the security implications:

#[cfg(windows)]
{
    tracing::warn!(
        "Database file permissions on Windows are not restricted. For security, ensure the database directory has appropriate access controls."
    );
    // Optionally check if file is in a user-only directory like AppData
}

The current implementation is fine for now, especially if Windows isn't a primary target platform.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/db.rs (5)

20-35: Add docstring to clarify platform-specific behavior

The restrict_file_permissions function lacks a docstring explaining its purpose and how it behaves differently across platforms. This is important since Windows users need to understand the security implications.

+/// Restricts file permissions to only allow access by the owner.
+/// 
+/// On Unix systems, this sets the permissions to 0o600 (owner read/write only).
+/// On Windows, this function only displays a warning as Windows permission model
+/// differs from Unix-like systems. Windows users should ensure proper ACLs are
+/// set manually or store the database in a secure location.
 fn restrict_file_permissions(path: &Path) -> Result<(), MostroError> {
     #[cfg(unix)]
     {
         let perms = Permissions::from_mode(0o600);
         set_permissions(path, perms)
             .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
     }

     #[cfg(windows)]
     {
         // Optional: could integrate with `winapi` or use a placeholder
-        println!("⚠️ Skipping permission change on Windows. Set it manually if needed.");
+        tracing::warn!("Database file permissions cannot be automatically restricted on Windows. Please ensure the file is stored in a secure location with appropriate access controls.");
     }

     Ok(())
 }

94-105: Avoid duplicate validation in is_strong_password

The function calls validate twice, which is inefficient. Store the validation result first, then check if it's empty.

 fn is_strong_password(&self, password: &str) -> bool {
-    match self.validate(password).is_empty() {
+    let failures = self.validate(password);
+    match failures.is_empty() {
         true => true,
         false => {
             println!("\nPassword is not strong enough:");
-            for failure in self.validate(password) {
+            for failure in failures {
                 println!("- {}", failure);
             }
             false
         }
     }
 }

768-815: Consider indexing for better encrypted lookup performance

The current approach of fetching all orders with a given status and then decrypting each key could become a performance bottleneck as the database grows.

Consider these performance optimizations:

  1. Add database indices on the status field to speed up the initial query
  2. Process batches of orders in parallel using Tokio's join operations
  3. Consider a caching mechanism for frequently accessed decrypted keys
// Example of parallel processing with Tokio
let decryption_tasks = orders_to_check
    .into_iter()
    .map(|master_key| {
        let password = MOSTRO_DB_PASSWORD.get().clone();
        tokio::spawn(async move {
            CryptoUtils::decrypt_data(master_key, Some(password))
        })
    })
    .collect::<Vec<_>>();

// Wait for all tasks to complete
for task in decryption_tasks {
    if let Ok(Ok(decrypted)) = task.await {
        if decrypted == pubkey {
            return Ok(true);
        }
    }
}

791-799: Use early return for decryption error handling

The code currently unwraps the decryption result with map_err and ?, which will return early if there's an error. For better error handling and visibility, consider using a more explicit pattern that logs decryption failures.

 // Decrypt master pubkey
-let master_pubkey_decrypted =
-    CryptoUtils::decrypt_data(master_key, MOSTRO_DB_PASSWORD.get())
-        .map_err(MostroInternalErr)?;
-if master_pubkey_decrypted == pubkey {
-    return Ok(true);
+match CryptoUtils::decrypt_data(master_key, MOSTRO_DB_PASSWORD.get()) {
+    Ok(master_pubkey_decrypted) => {
+        if master_pubkey_decrypted == pubkey {
+            return Ok(true);
+        }
+    },
+    Err(e) => {
+        tracing::warn!("Failed to decrypt master key: {}", e);
+        // Continue with next key instead of failing the entire operation
+    }
 }

141-207: Consider extracting common password input logic

The password input code in get_user_password and check_password_hash could be extracted into a shared helper function to reduce duplication.

/// Reads a password from stdin with anti-timing attack measures
fn read_password_secure() -> Result<String, MostroError> {
    // Add a random delay to mitigate timing attacks
    let random_delay = rand::random::<u16>() % 1000;
    
    let password = read_password()
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
    
    // Add delay after password input
    std::thread::sleep(std::time::Duration::from_millis(
        100_u64 + random_delay as u64,
    ));
    
    Ok(password)
}

This function could then be used in both places to reduce code duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8008197 and f172443.

📒 Files selected for processing (1)
  • src/db.rs (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (4)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:0-0
Timestamp: 2025-05-08T13:07:29.484Z
Learning: For the database encryption in Mostro, the developer prefers caching only derived encryption keys rather than caching decrypted master keys in RAM, as a security measure to reduce the attack surface from potential memory dumps or runtime vulnerabilities.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:315-354
Timestamp: 2025-05-08T18:10:03.818Z
Learning: In the Mostro codebase, deterministic salt generation is used intentionally in testing functions to benchmark decryption timing and test caching mechanisms. This is a valid testing approach when consistent, reproducible results are needed for performance evaluation.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests
🔇 Additional comments (5)
src/db.rs (5)

86-89: Good: Empty password handling is now commented

The empty password handling has a clear comment explaining that it's intentional to support optional encryption. This is an improvement from earlier versions of the code.


108-139: Good: Implemented anti-timing attack measures

The addition of a random delay before password verification is a good security practice to mitigate brute force attempts. The code also properly handles password verification and storage in the global OnceCell.


323-328: Deterministic salt generation is appropriate for testing

While deterministic salts would be concerning in production code, this is appropriate for testing since it allows for consistent benchmark results. The code also clearly documents that these are test functions.


913-1038: Good test implementation for database encryption performance

The test function effectively measures decryption performance and validates the encryption/decryption functionality. It uses fixed salts for reproducible test results, appropriate for test environments.


804-813: Improved query for unencrypted database mode

The code efficiently uses query_scalar with EXISTS for the unencrypted case, which is more efficient than retrieving full records. This is a good optimization.

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @arkanoider
when admin tries to take a dispute of child order in privacy mode

2025-05-20T05:43:23.859013Z  WARN mostrod::app: Error in AdminTakeDispute with context Error in database access: no rows returned by a query that expected to return at least one row - inner message no rows returned by a query that expected to return at least one row

@arkanoider

Copy link
Copy Markdown
Collaborator Author

Hi @arkanoider when admin tries to take a dispute of child order in privacy mode

2025-05-20T05:43:23.859013Z  WARN mostrod::app: Error in AdminTakeDispute with context Error in database access: no rows returned by a query that expected to return at least one row - inner message no rows returned by a query that expected to return at least one row

Great pick! I will check @Catrya , so just when the dispute is about a child order.

@Catrya

Catrya commented May 20, 2025

Copy link
Copy Markdown
Member

But when is it in privacy mode.
In normal mode mostro-cli panics so I can't test mostrod in that case

Catrya

This comment was marked as duplicate.

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @arkanoider When republishing a range order, in event 38383 of the pending order the reputation label is not displayed even though it has reputation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/db.rs (2)

113-117: ⚠️ Potential issue

Critical security issue: Hardcoded password in production code

The function uses a hardcoded password ("PippoPippo#75") instead of reading it securely from user input. This is a major security vulnerability as it exposes the database encryption key in plaintext within the source code.

Replace the hardcoded password with the commented-out secure password reading implementation:

-let password = "PippoPippo#75";
-// Simulate a delay in password input to avoid timing attacks
-// let random_delay = rand::random::<u16>() % 1000;
-// let password = read_password()
-//     .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+print!("Enter database password: ");
+std::io::stdout().flush().unwrap();
+let password = read_password()
+    .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+
+// Add a small delay to mitigate brute force attempts
+let random_delay = rand::random::<u16>() % 1000;
+std::thread::sleep(std::time::Duration::from_millis(
+    100_u64 + random_delay as u64,
+));

21-36: 🛠️ Refactor suggestion

File permission security is incomplete for Windows systems

The restrict_file_permissions function properly restricts permissions on Unix systems (mode 0o600), but only prints a warning on Windows without actually securing the file. This could leave the database file accessible to other users on Windows systems.

Consider either:

  1. Implementing proper Windows ACL handling to restrict permissions, or
  2. Enhancing the warning to use tracing::warn! for better visibility and adding more details:
#[cfg(windows)]
{
-    // Optional: could integrate with `winapi` or use a placeholder
-    println!("⚠️ Skipping permission change on Windows. Set it manually if needed.");
+    tracing::warn!(
+        "Database file permissions on Windows are not restricted. For security, ensure the database directory has appropriate access controls."
+    );
}
🧹 Nitpick comments (1)
src/db.rs (1)

776-810: Consider caching decrypted results for better performance

The implementation decrypts every order's key when checking for pending orders, which could become slow as the number of stored orders grows.

Consider adding a time-limited cache for decrypted keys to improve performance:

// At module level:
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, Instant};

lazy_static! {
    static ref DECRYPTION_CACHE: Mutex<HashMap<String, (String, Instant)>> = 
        Mutex::new(HashMap::new());
}

// In the function:
let master_pubkey_decrypted = {
    // Try cache first with a TTL of 5 minutes
    let mut cache = DECRYPTION_CACHE.lock().unwrap();
    if let Some((cached_value, timestamp)) = cache.get(&master_key) {
        if timestamp.elapsed() < Duration::from_secs(300) {
            cached_value.clone()
        } else {
            // Expired, remove and decrypt again
            cache.remove(&master_key);
            let decrypted = CryptoUtils::decrypt_data(master_key.clone(), MOSTRO_DB_PASSWORD.get())
                .map_err(MostroInternalErr)?;
            cache.insert(master_key.clone(), (decrypted.clone(), Instant::now()));
            decrypted
        }
    } else {
        // Not in cache, decrypt and cache
        let decrypted = CryptoUtils::decrypt_data(master_key.clone(), MOSTRO_DB_PASSWORD.get())
            .map_err(MostroInternalErr)?;
        cache.insert(master_key.clone(), (decrypted.clone(), Instant::now()));
        decrypted
    }
};

Based on the retrieved learnings, I see you already have caching for derived encryption keys which should help performance. If this proves to be sufficient, the additional cache layer may not be necessary.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f172443 and 5d28080.

📒 Files selected for processing (2)
  • src/app/release.rs (6 hunks)
  • src/db.rs (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/release.rs
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (4)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:0-0
Timestamp: 2025-05-08T13:07:29.484Z
Learning: For the database encryption in Mostro, the developer prefers caching only derived encryption keys rather than caching decrypted master keys in RAM, as a security measure to reduce the attack surface from potential memory dumps or runtime vulnerabilities.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:315-354
Timestamp: 2025-05-08T18:10:03.818Z
Learning: In the Mostro codebase, deterministic salt generation is used intentionally in testing functions to benchmark decryption timing and test caching mechanisms. This is a valid testing approach when consistent, reproducible results are needed for performance evaluation.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests
🔇 Additional comments (5)
src/db.rs (5)

87-90: Good explanation for empty password handling

The comment clarifying that empty passwords are allowed for optional encryption is a good practice, making the design decision clear to future readers.


168-171: Secure implementation of delay to prevent timing attacks

Adding a randomized delay after password input is an excellent security practice to prevent timing attacks. The combination of a fixed base delay (100ms) plus random component makes it difficult for attackers to determine password validity through timing differences.


292-311: Good security practice: Limited password attempts

Limiting password attempts to 3 tries before exiting the application is an effective protection against brute force attacks. This is particularly appropriate for a desktop application where legitimate users should know their password.


324-333: Proper cryptographic practices for password hashing

The implementation correctly uses:

  1. Cryptographically secure random salt generation with SaltString::generate(&mut OsRng)
  2. Argon2 for password hashing, which is a memory-hard function resistant to hardware acceleration attacks
  3. Proper error handling for cryptographic operations

909-1035: Well-designed test for encryption functionality

The test module effectively validates the encryption/decryption functionality:

  1. Uses in-memory SQLite for fast, isolated testing
  2. Properly tests both encryption and decryption paths
  3. Provides performance measurements for decryption operations
  4. Verifies expected outcomes with clear assertions

The deterministic salt generation in the test is appropriate for reproducible results and benchmarking the caching mechanism, as indicated in the retrieved learnings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/db.rs (1)

87-90: Empty password handling bypasses validation

The function skips all validations if the password is empty, but the intention isn't immediately clear from the code.

Add a comment explaining the design decision:

// If password is empty, clear failures
if password.is_empty() {
+   // Empty password is allowed to support optional encryption
    failures.clear();
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d28080 and 01da1e4.

📒 Files selected for processing (1)
  • src/db.rs (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/db.rs (4)
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:792-802
Timestamp: 2025-04-27T20:07:24.558Z
Learning: The decrypt_data function from mostro_core::order returns a type that needs to be wrapped with MostroInternalErr to convert it to MostroError, so calls should keep the .map_err(MostroInternalErr) conversion.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:122-145
Timestamp: 2025-04-28T13:21:36.613Z
Learning: Adding a small delay (e.g., 100ms) before password verification operations is a good security practice to mitigate brute force attacks by limiting the rate at which an attacker can attempt different passwords.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:0-0
Timestamp: 2025-05-08T13:07:29.484Z
Learning: For the database encryption in Mostro, the developer prefers caching only derived encryption keys rather than caching decrypted master keys in RAM, as a security measure to reduce the attack surface from potential memory dumps or runtime vulnerabilities.
Learnt from: arkanoider
PR: MostroP2P/mostro#475
File: src/db.rs:315-354
Timestamp: 2025-05-08T18:10:03.818Z
Learning: In the Mostro codebase, deterministic salt generation is used intentionally in testing functions to benchmark decryption timing and test caching mechanisms. This is a valid testing approach when consistent, reproducible results are needed for performance evaluation.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests
🔇 Additional comments (8)
src/db.rs (8)

3-9: Strong security foundation with appropriate cryptographic libraries

You've chosen excellent libraries for secure password management and data encryption:

  • Argon2 for password hashing (memory-hard KDF resistant to hardware attacks)
  • SecretString for protecting sensitive data in memory
  • Read_password for secure terminal input

113-121: Excellent timing attack mitigation

Adding both a random and fixed delay before password verification helps mitigate timing attacks while maintaining a minimum delay for brute force protection.


294-310: Good password attempt limiting

Limiting password attempts to 3 tries before exiting the application is an effective defense against brute force attacks.


631-651: Admin password retrieval implementation is secure

The get_admin_password function properly retrieves the stored password hash while maintaining appropriate error handling and avoiding exposing the hash unnecessarily.


789-790: Correct error conversion for CryptoUtils::decrypt_data

The error handling for decrypt_data correctly maps the error to MostroInternalErr based on the retrieved learning that it returns a type that needs this conversion to become a MostroError.


908-1034: Comprehensive encryption testing

The test module thoroughly validates the encryption/decryption functionality with:

  • Proper setup of an in-memory database
  • Testing with a variety of entries
  • Performance timing for decryption operations
  • Verification of both encryption and decryption

955-959: Deterministic salt generation in tests

The test uses deterministic salt generation based on a fixed value and index modulo, which is appropriate for testing but would be insecure in production code.

This is perfectly fine for testing purposes where reproducible results are needed.


1000-1004: Performance measurement in tests

Including timing measurements for decryption operations in tests is excellent for benchmarking performance of the encryption implementation.

Comment thread src/db.rs
Comment thread src/db.rs
…rmation also if order is normal (not full privacy)
@arkanoider

Copy link
Copy Markdown
Collaborator Author

@Catrya last commit should fix the missing user information in a child order when the creator of the order is not in full privacy mode. Please test it when you're available, will do the same tonite at home.

@Catrya

Catrya commented May 22, 2025

Copy link
Copy Markdown
Member

Hi @arkanoider if the order is in privacy mode, with range, whe is republished is mising in the 38383 event:

      [
        "rating",
        "[\"rating\",{\"days\":0,\"total_rating\":0.0,\"total_reviews\":0}]"
      ],

@arkanoider

Copy link
Copy Markdown
Collaborator Author

Hi @arkanoider if the order is in privacy mode, with range, whe is republished is mising in the 38383 event:

      [
        "rating",
        "[\"rating\",{\"days\":0,\"total_rating\":0.0,\"total_reviews\":0}]"
      ],

This should be fixed with last commit @Catrya ! Thanks for founding a way to replicate the encryption error...great job! Tomorrow I will analyze it!

- when I recreate the child order master keys were saved without encryption
  so when a takesell or takebuy comes in the search for pending user orders generate a decrytpion error.
@arkanoider

Copy link
Copy Markdown
Collaborator Author

@Catrya i found the cause of decryption error, last commit has the fix. When you want and you can test it let me know if it's ok also for you!

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

tACK is ready for merge

@grunch grunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

utACK

@grunch grunch merged commit df093c9 into main May 29, 2025
2 checks passed
@arkanoider

Copy link
Copy Markdown
Collaborator Author

utACK

Finallyyyyy!!!! Great @grunch also #OFF can't stop you from mostro...thanks this is good to move on with other things.

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.

4 participants