mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Redact secrets from pipeline event output (NDJSON, live.json, SSE)
Apply redact::redact_jsonl_line at all three serialization sites so secrets (AWS keys, GitHub PATs, private keys, etc.) are scrubbed before reaching disk or the network. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: e23c92750cd9
This commit is contained in:
parent
5067a59f7b
commit
d47c8e9048
4 changed files with 49 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -207,6 +207,7 @@ dependencies = [
|
|||
"nom",
|
||||
"predicates",
|
||||
"rand 0.8.5",
|
||||
"redact",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ dotenvy.workspace = true
|
|||
agent = { path = "../agent" }
|
||||
terminal = { path = "../terminal" }
|
||||
llm = { path = "../llm" }
|
||||
redact = { path = "../redact" }
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
});
|
||||
// Append to progress.ndjson
|
||||
if let Ok(line) = serde_json::to_string(&envelope) {
|
||||
let line = redact::redact_jsonl_line(&line);
|
||||
use std::io::Write;
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
|
|
@ -130,6 +131,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
}
|
||||
// Overwrite live.json
|
||||
if let Ok(pretty) = serde_json::to_string_pretty(&envelope) {
|
||||
let pretty = redact::redact_jsonl_line(&pretty);
|
||||
let _ = std::fs::write(&live_path, pretty);
|
||||
}
|
||||
});
|
||||
|
|
@ -330,3 +332,47 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn redact_removes_aws_key_from_compact_json() {
|
||||
let envelope = serde_json::json!({
|
||||
"timestamp": "2025-01-01T00:00:00.000Z",
|
||||
"run_id": "abc-123",
|
||||
"event": {
|
||||
"type": "agent",
|
||||
"content": "My key is AKIAYRWQG5EJLPZLBYNP and secret is wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||
}
|
||||
});
|
||||
let compact = serde_json::to_string(&envelope).unwrap();
|
||||
let redacted = redact::redact_jsonl_line(&compact);
|
||||
|
||||
assert!(!redacted.contains("AKIAYRWQG5EJLPZLBYNP"));
|
||||
assert!(redacted.contains("REDACTED"));
|
||||
|
||||
let parsed: serde_json::Value = serde_json::from_str(&redacted).unwrap();
|
||||
assert_eq!(parsed["run_id"], "abc-123");
|
||||
assert_eq!(parsed["timestamp"], "2025-01-01T00:00:00.000Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redact_removes_aws_key_from_pretty_json() {
|
||||
let envelope = serde_json::json!({
|
||||
"timestamp": "2025-01-01T00:00:00.000Z",
|
||||
"run_id": "def-456",
|
||||
"event": {
|
||||
"type": "agent",
|
||||
"content": "Credentials: AKIAYRWQG5EJLPZLBYNP"
|
||||
}
|
||||
});
|
||||
let pretty = serde_json::to_string_pretty(&envelope).unwrap();
|
||||
let redacted = redact::redact_jsonl_line(&pretty);
|
||||
|
||||
assert!(!redacted.contains("AKIAYRWQG5EJLPZLBYNP"));
|
||||
assert!(redacted.contains("REDACTED"));
|
||||
|
||||
let parsed: serde_json::Value = serde_json::from_str(&redacted).unwrap();
|
||||
assert_eq!(parsed["run_id"], "def-456");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ async fn get_events(
|
|||
let stream = BroadcastStream::new(rx).filter_map(|result| match result {
|
||||
Ok(event) => {
|
||||
let data = serde_json::to_string(&event).unwrap_or_default();
|
||||
let data = redact::redact_jsonl_line(&data);
|
||||
Some(Ok::<Event, std::convert::Infallible>(
|
||||
Event::default().data(data),
|
||||
))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue