security: fix Bitwarden password exposure and ChaCha nonce hardening

Issue 12: pass bw unlock password via stdin (--passwordstdin) instead of
as a CLI arg, preventing it from appearing in /proc/<pid>/cmdline or ps output.

Issue 11: replace Nonce::default() (all-zero) with the first 12 bytes of the
16-byte random payload nonce. The key-nonce pair was already unique per message
(HKDF salt), but using an explicit non-zero nonce removes the latent risk of
accidental key reuse and makes the safety invariant self-documenting.

Closes #11, closes #12
This commit is contained in:
Joseph O'Brien
2026-04-11 23:38:41 -04:00
parent 41d19a6988
commit 11d329c5a2
3 changed files with 30 additions and 6 deletions

View File

@@ -2,7 +2,8 @@ use crate::error::AgeError;
use crate::identities::Identity;
use crate::identities::x25519::X25519Identity;
use crate::sources::IdentitySource;
use std::process::Command;
use std::io::Write;
use std::process::{Command, Stdio};
pub struct BitwardenSource {
pub item_name: String,
@@ -37,13 +38,27 @@ impl BitwardenSource {
source: anyhow::anyhow!("could not read password: {e}"),
}
})?;
let output = Command::new("bw")
.args(["unlock", "--raw", &password])
.output()
// Pass password via stdin to avoid it appearing in the process list.
let mut child = Command::new("bw")
.args(["unlock", "--raw", "--passwordstdin"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| AgeError::SourceError {
name: self.name().to_string(),
source: anyhow::anyhow!("bw unlock spawn: {e}"),
})?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(password.as_bytes()).map_err(|e| AgeError::SourceError {
name: self.name().to_string(),
source: anyhow::anyhow!("bw unlock stdin write: {e}"),
})?;
}
let output = child.wait_with_output().map_err(|e| AgeError::SourceError {
name: self.name().to_string(),
source: anyhow::anyhow!("bw unlock wait: {e}"),
})?;
if !output.status.success() {
return Err(AgeError::SourceError {
name: self.name().to_string(),