use super::redact_event;
use crate::config::redact::REDACTION;
use crate::session::event::{
Delegated, EndReason, NoticeLevel, ReactionOutcome, SessionEvent, SessionUsage, ToolActivity,
ToolResult,
};
const SECRET: &str = "sk-live-9f3c7a11d4e6";
fn usage() -> SessionUsage {
SessionUsage {
input: 1,
output: 1,
cache_read: 0,
cache_write: 0,
total_tokens: 2,
cost: 0.0,
context_tokens: 1,
context_window: None,
turns: 1,
model: None,
}
}
fn calls() -> Vec<SessionEvent> {
let with_secret = |text: &str| format!("{text}{SECRET}");
vec![
SessionEvent::Post {
text: with_secret("the key is "),
},
SessionEvent::Notice {
text: with_secret("starting with "),
level: NoticeLevel::Started,
},
SessionEvent::Reply {
text: with_secret("output "),
command: with_secret("!cat "),
},
SessionEvent::ToolResult {
result: ToolResult {
id: "1".to_owned(),
name: "bash".to_owned(),
failed: false,
output: with_secret("env: "),
},
},
SessionEvent::Delegation {
delegated: Delegated {
question: with_secret("what is "),
model: Some("flash".to_owned()),
answer: Some(with_secret("it is ")),
..Default::default()
},
},
SessionEvent::BeginTurn { turn: 3 },
SessionEvent::Thinking {
text: with_secret("I should use "),
},
SessionEvent::Prompt {
author: "amelia".to_owned(),
text: with_secret("use "),
id: None,
withdrawn: false,
},
SessionEvent::Aside {
author: "amelia".to_owned(),
text: with_secret("never mind "),
id: None,
withdrawn: false,
},
SessionEvent::Activity {
line: with_secret("ran "),
tool: Some(ToolActivity {
id: None,
name: "bash".to_owned(),
target: with_secret("echo ").into(),
failed: None,
}),
},
SessionEvent::Diff {
path: format!("{SECRET}.ts"),
added: 1,
removed: 0,
body: with_secret("+ "),
cause: with_secret("write ").into(),
},
SessionEvent::Waiting {
text: with_secret("waiting on ").into(),
},
SessionEvent::Reaction {
message_id: "123456789".to_owned(),
outcome: ReactionOutcome::Succeeded,
},
SessionEvent::Usage { usage: usage() },
SessionEvent::Busy { busy: true },
SessionEvent::Upload {
name: format!("{SECRET}.txt"),
bytes: vec![1, 2],
caption: with_secret("here is "),
},
SessionEvent::Close {
reason: EndReason::Idle,
},
]
}
#[test]
fn nothing_a_session_reports_carries_the_credential_through() {
let secrets = [SECRET.to_owned()];
let all: Vec<String> = calls()
.into_iter()
.map(|event| {
serde_json::to_string(&redact_event(event, &secrets)).expect("an event serializes")
})
.collect();
let all = all.join("\n");
assert!(!all.contains(SECRET), "{all}");
assert!(all.contains(REDACTION));
}
#[test]
fn every_way_of_reporting_something_is_covered() {
let secrets = [SECRET.to_owned()];
let covered = calls()
.into_iter()
.map(|event| redact_event(event, &secrets))
.collect::<Vec<_>>();
assert_eq!(covered.len(), 17);
assert!(covered.iter().any(|event| matches!(
event,
SessionEvent::ToolResult { result } if result.output.contains(REDACTION)
)));
assert!(
covered
.iter()
.any(|event| matches!(event, SessionEvent::Usage { .. }))
);
}
#[test]
fn with_no_secrets_everything_is_passed_through_untouched() {
for event in calls() {
assert_eq!(redact_event(event.clone(), &[]), event);
}
}
#[test]
fn what_is_reported_is_otherwise_unchanged() {
let secrets = [SECRET.to_owned()];
let said = redact_event(
SessionEvent::Post {
text: "nothing secret here".to_owned(),
},
&secrets,
);
assert_eq!(
said,
SessionEvent::Post {
text: "nothing secret here".to_owned()
}
);
let busy = redact_event(SessionEvent::Busy { busy: true }, &secrets);
assert_eq!(busy, SessionEvent::Busy { busy: true });
}
#[test]
fn the_operators_house_rules_are_scrubbed_out_of_tool_output() {
let rules = "You are Talaria, the errand-runner.\n\nNever push unless asked.";
let secrets = [SECRET.to_owned(), rules.to_owned()];
let reported = redact_event(
SessionEvent::ToolResult {
result: ToolResult {
id: "1".to_owned(),
name: "bash".to_owned(),
failed: false,
output: format!(
"$ cat /state/memory.md\n## House rules\n\n{rules}\n\ntotal 4 files"
),
},
},
&secrets,
);
let said = serde_json::to_string(&reported).expect("an event serializes");
assert!(!said.contains("errand-runner"));
assert!(!said.contains("Never push unless asked"));
assert!(said.contains(REDACTION));
assert!(said.contains("total 4 files"));
assert!(said.contains("cat /state/memory.md"));
}