Severity
P2 — first poisoned read crashes every subsequent LLM turn with a budget check.
Files
crates/librefang-kernel/src/kernel/mod.rs:756
crates/librefang-kernel/src/kernel/mod.rs:765
Finding
budget_config() and update_budget_config() use
self.budget_config.read().unwrap() / .write().unwrap(). If any thread panics
while holding the write guard (e.g., the FnOnce closure in
update_budget_config panics — it's user-supplied via f), the lock becomes
poisoned and every subsequent budget read panics, including the hot path
called on every LLM turn for cost gating.
Other places in this same file already use
unwrap_or_else(|e| e.into_inner()) (e.g., skill_registry.read() at line 5927),
demonstrating awareness — budget_config was not migrated.
Evidence
// kernel/mod.rs:755-767
pub fn budget_config(&self) -> librefang_types::config::BudgetConfig {
self.budget_config.read().unwrap().clone()
}
pub fn update_budget_config(&self, f: impl FnOnce(&mut BudgetConfig)) {
let mut guard = self.budget_config.write().unwrap();
f(&mut guard); // user-supplied closure — panic poisons the lock
}
Suggested Fix
Replace both with .read().unwrap_or_else(|e| e.into_inner()) and
.write().unwrap_or_else(|e| e.into_inner()), matching the pattern at
line 5927. Optionally wrap the FnOnce in
std::panic::AssertUnwindSafe + catch_unwind to surface budget-mutation panics
as errors instead of poison.
Severity
P2 — first poisoned read crashes every subsequent LLM turn with a budget check.
Files
crates/librefang-kernel/src/kernel/mod.rs:756crates/librefang-kernel/src/kernel/mod.rs:765Finding
budget_config()andupdate_budget_config()useself.budget_config.read().unwrap()/.write().unwrap(). If any thread panicswhile holding the write guard (e.g., the
FnOnceclosure inupdate_budget_configpanics — it's user-supplied viaf), the lock becomespoisoned and every subsequent budget read panics, including the hot path
called on every LLM turn for cost gating.
Other places in this same file already use
unwrap_or_else(|e| e.into_inner())(e.g.,skill_registry.read()at line 5927),demonstrating awareness —
budget_configwas not migrated.Evidence
Suggested Fix
Replace both with
.read().unwrap_or_else(|e| e.into_inner())and.write().unwrap_or_else(|e| e.into_inner()), matching the pattern atline 5927. Optionally wrap the
FnOnceinstd::panic::AssertUnwindSafe + catch_unwindto surface budget-mutation panicsas errors instead of poison.