From 9b47dbbaed46185aec08e792cbf4015a8bb017cd Mon Sep 17 00:00:00 2001 From: Gabe Hamilton Date: Wed, 4 Mar 2026 01:21:19 -0700 Subject: [PATCH] fix(security): replace .unwrap() panics in pairing store with proper error handling (#515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pairing store called .unwrap() on path.parent() in three locations (upsert_request, record_failed_approve, add_allow_from). If a path has no parent (root path or empty), this panics — a potential denial-of-service vector if an attacker can influence the path. Added InvalidPath variant to PairingStoreError and replaced all three .unwrap() calls with ok_or_else error propagation. This follows the project's no-panics-in-production policy. Locations fixed: - upsert_request (line ~227) - record_failed_approve (line ~322) - add_allow_from (line ~465) Co-authored-by: Claude Sonnet 4.6 --- src/pairing/store.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pairing/store.rs b/src/pairing/store.rs index c0175688..8a44f3b1 100644 --- a/src/pairing/store.rs +++ b/src/pairing/store.rs @@ -30,6 +30,9 @@ pub enum PairingStoreError { #[error("Invalid channel: {0}")] InvalidChannel(String), + #[error("Invalid path: {0}")] + InvalidPath(String), + #[error("IO error: {0}")] Io(#[from] std::io::Error), @@ -224,7 +227,10 @@ impl PairingStore { meta: Option, ) -> Result { let path = pairing_path(&self.base_dir, channel)?; - fs::create_dir_all(path.parent().unwrap())?; + let parent = path.parent().ok_or_else(|| { + PairingStoreError::InvalidPath(format!("path has no parent: {}", path.display())) + })?; + fs::create_dir_all(parent)?; let mut file = fs::OpenOptions::new() .read(true) @@ -319,7 +325,10 @@ impl PairingStore { fn record_failed_approve(&self, channel: &str) -> Result<(), PairingStoreError> { let path = approve_attempts_path(&self.base_dir, channel)?; - fs::create_dir_all(path.parent().unwrap())?; + let parent = path.parent().ok_or_else(|| { + PairingStoreError::InvalidPath(format!("path has no parent: {}", path.display())) + })?; + fs::create_dir_all(parent)?; // Open (or create) and lock before reading so concurrent callers // don't clobber each other's writes. @@ -462,7 +471,10 @@ impl PairingStore { } let path = allow_from_path(&self.base_dir, channel)?; - fs::create_dir_all(path.parent().unwrap())?; + let parent = path.parent().ok_or_else(|| { + PairingStoreError::InvalidPath(format!("path has no parent: {}", path.display())) + })?; + fs::create_dir_all(parent)?; let file = fs::OpenOptions::new() .read(true)