From 5f961f0723b3c65e43813f521777cfe3d1dd6bf4 Mon Sep 17 00:00:00 2001 From: Fabro Date: Wed, 1 Jul 2026 16:49:03 +0000 Subject: [PATCH] fabro(01KWF7MM3VPXZZA8BTHJXE9VT1): simplify_opus (succeeded) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fabro-Run: 01KWF7MM3VPXZZA8BTHJXE9VT1 Fabro-Completed: 6 Fabro-Checkpoint: d34cb7e587df954ec5ebb8a51957ce7ceb81b563 ⚒️ Generated with [Fabro](https://fabro.sh) --- lib/crates/fabro-redact/src/lib.rs | 7 ++ .../fabro-redact/src/secret_registry.rs | 78 +++++++------------ 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/lib/crates/fabro-redact/src/lib.rs b/lib/crates/fabro-redact/src/lib.rs index d55f45f43..dd5da867b 100644 --- a/lib/crates/fabro-redact/src/lib.rs +++ b/lib/crates/fabro-redact/src/lib.rs @@ -45,7 +45,14 @@ pub struct Region { pub fn redact_string(s: &str) -> String { let mut regions = entropy::find_entropy_regions(s); regions.extend(gitleaks::find_gitleaks_regions(s)); + redact_regions(s, regions) +} +/// Replace each region of `s` with [`REDACTION_MARKER`]. +/// +/// Regions may be unsorted and overlapping; they are sorted by start and +/// overlapping regions are merged so the union is redacted as a single marker. +pub(crate) fn redact_regions(s: &str, mut regions: Vec) -> String { if regions.is_empty() { return s.to_string(); } diff --git a/lib/crates/fabro-redact/src/secret_registry.rs b/lib/crates/fabro-redact/src/secret_registry.rs index 77879018e..d28dd7873 100644 --- a/lib/crates/fabro-redact/src/secret_registry.rs +++ b/lib/crates/fabro-redact/src/secret_registry.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, PoisonError, RwLock}; +use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; use serde_json::Value; @@ -26,23 +26,23 @@ impl SecretRedactor { return; } - let mut values = self.values.write().unwrap_or_else(PoisonError::into_inner); - if !values.iter().any(|registered| registered == &value) { + let mut values = self.write(); + if !values.contains(&value) { values.push(value); } } /// Return `true` when no secret values have been registered. pub fn is_empty(&self) -> bool { - self.values - .read() - .unwrap_or_else(PoisonError::into_inner) - .is_empty() + self.read().is_empty() } /// Redact all registered secret values from `s`. pub fn redact_into(&self, s: &str) -> String { - let values = self.registered_values_longest_first(); + let values = self.read(); + if values.is_empty() { + return s.to_string(); + } redact_string_values(s, &values) } @@ -50,33 +50,34 @@ impl SecretRedactor { /// /// Object keys are left unchanged. pub fn redact_json(&self, mut value: Value) -> Value { - let values = self.registered_values_longest_first(); + let values = self.read(); if values.is_empty() { return value; } - redact_json_value(&mut value, &values); + redact_json_leaves(&mut value, &values); value } - fn registered_values_longest_first(&self) -> Vec { - let values = self.values.read().unwrap_or_else(PoisonError::into_inner); - let mut values = values.clone(); - values.sort_by(|left, right| right.len().cmp(&left.len()).then_with(|| left.cmp(right))); - values + fn read(&self) -> RwLockReadGuard<'_, Vec> { + self.values.read().unwrap_or_else(PoisonError::into_inner) + } + + fn write(&self) -> RwLockWriteGuard<'_, Vec> { + self.values.write().unwrap_or_else(PoisonError::into_inner) } } -fn redact_json_value(value: &mut Value, values: &[String]) { +fn redact_json_leaves(value: &mut Value, values: &[String]) { match value { Value::Object(obj) => { for child in obj.values_mut() { - redact_json_value(child, values); + redact_json_leaves(child, values); } } Value::Array(arr) => { for child in arr { - redact_json_value(child, values); + redact_json_leaves(child, values); } } Value::String(text) => { @@ -89,43 +90,24 @@ fn redact_json_value(value: &mut Value, values: &[String]) { } } +/// Collect every match of each registered value and let +/// [`crate::redact_regions`] sort and merge overlaps, so a secret that overlaps +/// another is fully redacted. +/// +/// Assumes a small number of registered values (bounded by the run's declared +/// secrets), so the per-value scan is not optimized further. fn redact_string_values(s: &str, values: &[String]) -> String { - if values.is_empty() { - return s.to_string(); - } - let mut regions = Vec::new(); for value in values { for (start, _) in s.match_indices(value) { - let end = start + value.len(); - if !regions - .iter() - .any(|region: &Region| regions_overlap(start, end, region)) - { - regions.push(Region { start, end }); - } + regions.push(Region { + start, + end: start + value.len(), + }); } } - if regions.is_empty() { - return s.to_string(); - } - - regions.sort_by_key(|region| region.start); - - let mut result = String::with_capacity(s.len()); - let mut previous = 0; - for region in ®ions { - result.push_str(&s[previous..region.start]); - result.push_str(crate::REDACTION_MARKER); - previous = region.end; - } - result.push_str(&s[previous..]); - result -} - -fn regions_overlap(start: usize, end: usize, region: &Region) -> bool { - start < region.end && region.start < end + crate::redact_regions(s, regions) } #[cfg(test)]