Skip to content

Commit d03da8a

Browse files
🛡️ Sentinel: [MEDIUM] Fix insecure file permissions on lockfile
🚨 Severity: MEDIUM 💡 Vulnerability: The `ProfileLock` struct creates lock files and their parent directories using default system permissions (`fs::create_dir_all` and `fs::OpenOptions::new()`), which could expose sensitive config paths or metadata on shared systems. 🎯 Impact: Local privilege escalation or info leakage of profile presence/metadata if executed on a shared multi-user machine. 🔧 Fix: Updated `ProfileLock::acquire` to use `crate::paths::create_secure_dir_all` (mode `0o700`) for the parent directory and explicitly set mode `0o600` for the lockfile itself via `OpenOptionsExt` on Unix platforms. Also cleaned up `cargo fmt` errors across codebase. ✅ Verification: `cargo test`, `cargo clippy`, and `cargo fmt` execute successfully. Check `.lock` file permissions after running the CLI. Co-authored-by: bitcoiner-dev <75873427+bitcoiner-dev@users.noreply.github.com>
1 parent 7b76cd4 commit d03da8a

File tree

12 files changed

+50
-38
lines changed

12 files changed

+50
-38
lines changed

src/commands/account.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::cli::{AccountAction, AccountArgs, Cli};
22
use crate::error::AppError;
3+
use crate::output::CommandOutput;
34
use crate::wallet_service::{now_unix, AccountState};
45
use crate::{load_wallet_session, profile_path, read_profile, write_profile};
5-
use crate::output::CommandOutput;
66
use zinc_core::Account;
77

88
pub async fn run(cli: &Cli, args: &AccountArgs) -> Result<CommandOutput, AppError> {

src/commands/address.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::cli::{AddressArgs, AddressKind, Cli};
22
use crate::error::AppError;
3+
use crate::output::CommandOutput;
34
use crate::wallet_service::map_wallet_error;
45
use crate::{load_wallet_session, persist_wallet_session};
5-
use crate::output::CommandOutput;
66

77
pub async fn run(cli: &Cli, args: &AddressArgs) -> Result<CommandOutput, AppError> {
88
let mut session = load_wallet_session(cli)?;

src/commands/doctor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::cli::Cli;
22
use crate::error::AppError;
3-
use crate::{profile_path, read_profile};
43
use crate::output::CommandOutput;
4+
use crate::{profile_path, read_profile};
55
use zinc_core::{OrdClient, ZincWallet};
66

77
pub async fn run(cli: &Cli) -> Result<CommandOutput, AppError> {

src/commands/inscription.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ pub async fn run(cli: &Cli, _args: &InscriptionArgs) -> Result<CommandOutput, Ap
1212
let display_items = if !cli.thumb_enabled() {
1313
None
1414
} else {
15-
Some(get_inscription_display_items(
16-
&session.profile.ord_url,
17-
&sorted_inscriptions,
18-
)
19-
.await)
15+
Some(get_inscription_display_items(&session.profile.ord_url, &sorted_inscriptions).await)
2016
};
2117

2218
Ok(CommandOutput::InscriptionList {

src/commands/lock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::cli::{Cli, LockAction, LockArgs};
22
use crate::error::AppError;
3+
use crate::output::CommandOutput;
34
use crate::wallet_service::now_unix;
45
use crate::{confirm, profile_lock_path, read_lock_metadata};
5-
use crate::output::CommandOutput;
66
use std::fs;
77

88
pub async fn run(cli: &Cli, args: &LockArgs) -> Result<CommandOutput, AppError> {

src/commands/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ pub mod setup;
1212
pub mod snapshot;
1313
pub mod sync;
1414
pub mod tx;
15+
pub mod version;
1516
pub mod wait;
1617
pub mod wallet;
17-
pub mod version;

src/commands/scenario.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::cli::{Cli, ScenarioAction, ScenarioArgs};
22
use crate::error::AppError;
3+
use crate::output::CommandOutput;
34
use crate::utils::run_bitcoin_cli;
45
use crate::wallet_service::NetworkArg;
56
use crate::{confirm, load_wallet_session, persist_wallet_session, snapshot_dir};
6-
use crate::output::CommandOutput;
77
use std::fs;
88

99
pub async fn run(cli: &Cli, args: &ScenarioArgs) -> Result<CommandOutput, AppError> {
@@ -121,9 +121,7 @@ pub async fn run(cli: &Cli, args: &ScenarioArgs) -> Result<CommandOutput, AppErr
121121
}
122122
}
123123

124-
CommandOutput::ScenarioReset {
125-
removed,
126-
}
124+
CommandOutput::ScenarioReset { removed }
127125
}
128126
};
129127

src/commands/wallet.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ pub async fn run(cli: &Cli, args: &WalletArgs) -> Result<CommandOutput, AppError
7979
bitcoin_cli: default_bitcoin_cli(),
8080
bitcoin_cli_args: default_bitcoin_cli_args().join(" "),
8181
phrase,
82-
words: if cli.reveal || !cli.agent { Some(wallet.words.len()) } else { None },
82+
words: if cli.reveal || !cli.agent {
83+
Some(wallet.words.len())
84+
} else {
85+
None
86+
},
8387
})
8488
}
8589
WalletAction::Import {
@@ -133,7 +137,11 @@ pub async fn run(cli: &Cli, args: &WalletArgs) -> Result<CommandOutput, AppError
133137
scheme: scheme_arg.to_string(),
134138
account_index: 0,
135139
imported: true,
136-
phrase: if cli.reveal || !cli.agent { Some(mnemonic.to_string()) } else { None },
140+
phrase: if cli.reveal || !cli.agent {
141+
Some(mnemonic.to_string())
142+
} else {
143+
None
144+
},
137145
})
138146
}
139147
WalletAction::Info => {

src/dashboard.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,7 @@ fn activate_session(
175175
state.network = Some(resolved_network.value);
176176
state.profile_name = Some(resolved_profile);
177177

178-
state.ordinals_address = Some(
179-
session
180-
.wallet
181-
.peek_taproot_address(0)
182-
.to_string(),
183-
);
178+
state.ordinals_address = Some(session.wallet.peek_taproot_address(0).to_string());
184179
state.payment_address = session
185180
.wallet
186181
.peek_payment_address(0)

src/lock.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ impl ProfileLock {
2222
let lock_path = profile_path.with_extension("lock");
2323

2424
if let Some(parent) = lock_path.parent() {
25-
fs::create_dir_all(parent).map_err(|e| {
25+
crate::paths::create_secure_dir_all(parent).map_err(|e| {
2626
crate::error::AppError::Config(format!("failed to create lock dir: {e}"))
2727
})?;
2828
}
2929

30-
let file = fs::OpenOptions::new()
31-
.write(true)
32-
.create_new(true)
30+
let mut options = fs::OpenOptions::new();
31+
options.write(true).create_new(true);
32+
33+
#[cfg(unix)]
34+
{
35+
use std::os::unix::fs::OpenOptionsExt;
36+
options.mode(0o600);
37+
}
38+
39+
let file = options
3340
.open(&lock_path)
3441
.map_err(|_| AppError::Config("profile is locked by another instance".to_string()))?;
3542

0 commit comments

Comments
 (0)