fix(security): replace .unwrap() panics in pairing store with proper error handling (#515)

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 <[email protected]>
This commit is contained in:
Gabe Hamilton
2026-03-04 08:21:19 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent ac3c928853
commit 9b47dbbaed
+15 -3
View File
@@ -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<serde_json::Value>,
) -> Result<UpsertResult, PairingStoreError> {
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)