fix(cli): init stages salt, handles --output ..-paths, zeroizes image_secret

1. Add .relicario/salt to the initial git commit so it syncs across
   devices (Argon2 salt must match at unlock time).
2. Return a proper error instead of panicking when --output has no
   filename component (e.g., trailing ..).
3. Wrap the generated 32-byte image_secret in Zeroizing for
   consistency with the passphrase + master_key handling in Task 4.

Caught in Task 6 review.
This commit is contained in:
adlee-was-taken
2026-04-19 22:11:10 -04:00
parent a50099a066
commit 5dce2c10f9

View File

@@ -282,11 +282,14 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
}
// Image secret: 32 random bytes, embedded in the carrier.
let mut image_secret = [0u8; 32];
OsRng.fill_bytes(&mut image_secret);
let image_secret = {
let mut buf = Zeroizing::new([0u8; 32]);
OsRng.fill_bytes(buf.as_mut_slice());
buf
};
let carrier = fs::read(&image)
.with_context(|| format!("failed to read carrier image {}", image.display()))?;
let stego = imgsecret::embed(&carrier, &image_secret)?;
let stego = imgsecret::embed(&carrier, &*image_secret)?;
fs::write(&output, &stego)
.with_context(|| format!("failed to write reference image {}", output.display()))?;
@@ -296,7 +299,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
let params = KdfParams { argon2_m: 65536, argon2_t: 3, argon2_p: 4 };
// Derive master key, then persist an empty Manifest + default VaultSettings.
let master_key = derive_master_key(passphrase.as_bytes(), &image_secret, &salt, &params)?;
let master_key = derive_master_key(passphrase.as_bytes(), &*image_secret, &salt, &params)?;
fs::create_dir_all(&relicario_dir)?;
fs::create_dir_all(root.join("items"))?;
@@ -324,7 +327,10 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
fs::write(root.join("settings.enc"), encrypt_settings(&settings, &master_key)?)?;
// .gitignore excludes the reference image.
let gitignore = format!("{}\n", output.file_name().unwrap().to_string_lossy());
let fname = output.file_name()
.ok_or_else(|| anyhow::anyhow!("output path has no filename: {}", output.display()))?
.to_string_lossy();
let gitignore = format!("{fname}\n");
fs::write(root.join(".gitignore"), gitignore)?;
// git init + initial commit via hardened wrapper.
@@ -332,7 +338,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
if !status.success() { anyhow::bail!("git init failed"); }
let _ = crate::helpers::git_command(&root, &[
"add", ".gitignore", ".relicario/params.json", ".relicario/devices.json",
"manifest.enc", "settings.enc",
".relicario/salt", "manifest.enc", "settings.enc",
]).status()?;
let status = crate::helpers::git_command(&root, &[
"commit", "-m", "init: new relicario vault (format v2)",