oh-my-codex 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +13 -5
- package/Cargo.toml +2 -1
- package/README.md +1 -0
- package/crates/omx-api/Cargo.toml +19 -0
- package/crates/omx-api/src/lib.rs +2940 -0
- package/crates/omx-api/src/main.rs +10 -0
- package/crates/omx-api/tests/cli.rs +558 -0
- package/crates/omx-explore/src/main.rs +4 -0
- package/crates/omx-sparkshell/src/codex_bridge.rs +437 -123
- package/crates/omx-sparkshell/src/exec.rs +4 -0
- package/crates/omx-sparkshell/src/main.rs +738 -29
- package/crates/omx-sparkshell/src/prompt.rs +25 -3
- package/crates/omx-sparkshell/src/redaction.rs +241 -0
- package/crates/omx-sparkshell/tests/execution.rs +479 -238
- package/dist/cli/__tests__/api.test.d.ts +2 -0
- package/dist/cli/__tests__/api.test.d.ts.map +1 -0
- package/dist/cli/__tests__/api.test.js +175 -0
- package/dist/cli/__tests__/api.test.js.map +1 -0
- package/dist/cli/__tests__/ask.test.js +72 -5
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js +14 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +23 -0
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +123 -5
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +76 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +138 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +5 -0
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
- package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/api.d.ts +26 -0
- package/dist/cli/api.d.ts.map +1 -0
- package/dist/cli/api.js +153 -0
- package/dist/cli/api.js.map +1 -0
- package/dist/cli/explore.d.ts +2 -0
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +43 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +10 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +128 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +2 -1
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +1 -0
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +20 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +90 -0
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
- package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +11 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +2 -2
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +23 -18
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +7 -6
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +75 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +3 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +71 -2
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +737 -26
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +4 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/build-api.d.ts +2 -0
- package/dist/scripts/build-api.d.ts.map +1 -0
- package/dist/scripts/build-api.js +44 -0
- package/dist/scripts/build-api.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +208 -8
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +89 -24
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +88 -0
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +27 -9
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +26 -11
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -0
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +38 -0
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +27 -14
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +9 -3
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +1 -1
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +2 -0
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +2 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +96 -19
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +34 -10
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/package.json +4 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +1 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/prompts/researcher.md +15 -10
- package/skills/best-practice-research/SKILL.md +83 -0
- package/skills/deep-interview/SKILL.md +1 -0
- package/skills/ralplan/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +810 -4
- package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
- package/src/scripts/__tests__/smoke-packed-install.test.ts +8 -2
- package/src/scripts/build-api.ts +48 -0
- package/src/scripts/codex-native-hook.ts +262 -10
- package/src/scripts/codex-native-pre-post.ts +103 -24
- package/src/scripts/notify-dispatcher.ts +97 -0
- package/src/scripts/notify-hook/team-dispatch.ts +27 -8
- package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
- package/src/scripts/notify-hook/team-tmux-guard.ts +42 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
- package/src/scripts/run-provider-advisor.ts +11 -3
- package/src/scripts/smoke-packed-install.ts +2 -0
- package/templates/catalog-manifest.json +7 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
use crate::exec::CommandOutput;
|
|
2
|
+
use crate::redaction::redact_output;
|
|
2
3
|
use std::borrow::Cow;
|
|
3
4
|
use std::env;
|
|
4
5
|
use std::path::Path;
|
|
@@ -111,8 +112,10 @@ fn command_basename(command: &str) -> Cow<'_, str> {
|
|
|
111
112
|
pub fn build_summary_prompt(command: &[String], output: &CommandOutput) -> String {
|
|
112
113
|
let executable = command.first().map(String::as_str).unwrap_or("unknown");
|
|
113
114
|
let family = select_command_family(executable);
|
|
114
|
-
let
|
|
115
|
-
let
|
|
115
|
+
let redacted = redact_output(output);
|
|
116
|
+
let safe_output = &redacted.output;
|
|
117
|
+
let stdout_text = safe_output.stdout_text();
|
|
118
|
+
let stderr_text = safe_output.stderr_text();
|
|
116
119
|
let stdout_lines = count_lines(&stdout_text);
|
|
117
120
|
let stderr_lines = count_lines(&stderr_text);
|
|
118
121
|
let stdout_excerpt = truncate_for_prompt(&stdout_text, "stdout");
|
|
@@ -141,7 +144,7 @@ pub fn build_summary_prompt(command: &[String], output: &CommandOutput) -> Strin
|
|
|
141
144
|
family_pattern = family.pattern,
|
|
142
145
|
family_description = family.description,
|
|
143
146
|
family_what_it_does = family.what_it_does,
|
|
144
|
-
exit_code =
|
|
147
|
+
exit_code = safe_output.exit_code(),
|
|
145
148
|
stdout_lines = stdout_lines,
|
|
146
149
|
stdout_bytes = stdout_text.len(),
|
|
147
150
|
stderr_lines = stderr_lines,
|
|
@@ -308,6 +311,25 @@ mod tests {
|
|
|
308
311
|
assert!(prompt.contains("<<<STDERR"));
|
|
309
312
|
}
|
|
310
313
|
|
|
314
|
+
#[test]
|
|
315
|
+
fn prompt_redacts_secret_like_stdout_and_stderr_before_embedding() {
|
|
316
|
+
let output = CommandOutput {
|
|
317
|
+
status: ok_status(),
|
|
318
|
+
stdout: b"API_TOKEN=super-secret\nsk-prod-secret\nvisible\n".to_vec(),
|
|
319
|
+
stderr: b"Authorization: Bearer bearer-secret\nghp_secret\n".to_vec(),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
let prompt = build_summary_prompt(&["env".into()], &output);
|
|
323
|
+
|
|
324
|
+
assert!(prompt.contains("API_TOKEN=[REDACTED]"));
|
|
325
|
+
assert!(prompt.contains("Authorization: Bearer [REDACTED]"));
|
|
326
|
+
assert!(prompt.contains("visible"));
|
|
327
|
+
assert!(!prompt.contains("super-secret"));
|
|
328
|
+
assert!(!prompt.contains("sk-prod-secret"));
|
|
329
|
+
assert!(!prompt.contains("bearer-secret"));
|
|
330
|
+
assert!(!prompt.contains("ghp_secret"));
|
|
331
|
+
}
|
|
332
|
+
|
|
311
333
|
#[test]
|
|
312
334
|
fn prompt_truncates_large_streams_before_embedding_them() {
|
|
313
335
|
let _guard = env_lock();
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
use crate::exec::CommandOutput;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone)]
|
|
4
|
+
pub(crate) struct RedactedOutput {
|
|
5
|
+
pub(crate) output: CommandOutput,
|
|
6
|
+
pub(crate) count: usize,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
pub(crate) fn redact_output(output: &CommandOutput) -> RedactedOutput {
|
|
10
|
+
let (stdout, stdout_count) = redact_bytes(&output.stdout);
|
|
11
|
+
let (stderr, stderr_count) = redact_bytes(&output.stderr);
|
|
12
|
+
RedactedOutput {
|
|
13
|
+
output: CommandOutput {
|
|
14
|
+
status: output.status,
|
|
15
|
+
stdout,
|
|
16
|
+
stderr,
|
|
17
|
+
},
|
|
18
|
+
count: stdout_count + stderr_count,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fn redact_bytes(bytes: &[u8]) -> (Vec<u8>, usize) {
|
|
23
|
+
let (text, count) = redact_text(&String::from_utf8_lossy(bytes));
|
|
24
|
+
(text.into_bytes(), count)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn redact_text(text: &str) -> (String, usize) {
|
|
28
|
+
let mut count = 0;
|
|
29
|
+
let mut lines = Vec::new();
|
|
30
|
+
for line in text.lines() {
|
|
31
|
+
let mut redacted = line.to_string();
|
|
32
|
+
count += redact_authorization_headers(&mut redacted);
|
|
33
|
+
count += redact_key_value_secrets(&mut redacted);
|
|
34
|
+
count += redact_secret_markers(&mut redacted);
|
|
35
|
+
lines.push(redacted);
|
|
36
|
+
}
|
|
37
|
+
let mut rendered = lines.join("\n");
|
|
38
|
+
if text.ends_with('\n') {
|
|
39
|
+
rendered.push('\n');
|
|
40
|
+
}
|
|
41
|
+
(rendered, count)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn redact_authorization_headers(text: &mut String) -> usize {
|
|
45
|
+
let mut count = 0;
|
|
46
|
+
let mut search_start = 0;
|
|
47
|
+
while search_start < text.len() {
|
|
48
|
+
let Some(relative_start) = text[search_start..]
|
|
49
|
+
.to_ascii_lowercase()
|
|
50
|
+
.find("authorization: bearer ")
|
|
51
|
+
else {
|
|
52
|
+
break;
|
|
53
|
+
};
|
|
54
|
+
let start = search_start + relative_start;
|
|
55
|
+
let value_start = start + "authorization: bearer ".len();
|
|
56
|
+
let end = find_secret_end(text, value_start);
|
|
57
|
+
if text.get(value_start..end) == Some("[REDACTED]") {
|
|
58
|
+
search_start = end;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
text.replace_range(value_start..end, "[REDACTED]");
|
|
62
|
+
count += 1;
|
|
63
|
+
search_start = value_start + "[REDACTED]".len();
|
|
64
|
+
}
|
|
65
|
+
count
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn redact_key_value_secrets(text: &mut String) -> usize {
|
|
69
|
+
let mut count = 0;
|
|
70
|
+
let secret_keys = [
|
|
71
|
+
"access_token",
|
|
72
|
+
"api_key",
|
|
73
|
+
"apikey",
|
|
74
|
+
"auth_token",
|
|
75
|
+
"password",
|
|
76
|
+
"secret",
|
|
77
|
+
"token",
|
|
78
|
+
];
|
|
79
|
+
let mut search_start = 0;
|
|
80
|
+
loop {
|
|
81
|
+
if search_start >= text.len() {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
let lower = text[search_start..].to_ascii_lowercase();
|
|
85
|
+
let Some((relative_key_start, key)) = secret_keys
|
|
86
|
+
.iter()
|
|
87
|
+
.filter_map(|key| lower.find(key).map(|index| (index, *key)))
|
|
88
|
+
.min_by_key(|(index, _)| *index)
|
|
89
|
+
else {
|
|
90
|
+
break;
|
|
91
|
+
};
|
|
92
|
+
let key_start = search_start + relative_key_start;
|
|
93
|
+
let key_end = key_start + key.len();
|
|
94
|
+
let after_key = &text[key_end..];
|
|
95
|
+
let Some(delimiter_offset) = after_key.char_indices().find_map(|(offset, ch)| match ch {
|
|
96
|
+
'=' | ':' => Some(offset),
|
|
97
|
+
'"' | '\'' | ' ' | '\t' => None,
|
|
98
|
+
_ => Some(usize::MAX),
|
|
99
|
+
}) else {
|
|
100
|
+
break;
|
|
101
|
+
};
|
|
102
|
+
if delimiter_offset == usize::MAX {
|
|
103
|
+
search_start = key_end;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
let delimiter = key_end + delimiter_offset;
|
|
107
|
+
let mut value_start = delimiter + 1;
|
|
108
|
+
while text
|
|
109
|
+
.as_bytes()
|
|
110
|
+
.get(value_start)
|
|
111
|
+
.is_some_and(u8::is_ascii_whitespace)
|
|
112
|
+
{
|
|
113
|
+
value_start += 1;
|
|
114
|
+
}
|
|
115
|
+
let quote = text
|
|
116
|
+
.as_bytes()
|
|
117
|
+
.get(value_start)
|
|
118
|
+
.copied()
|
|
119
|
+
.filter(|byte| *byte == b'"' || *byte == b'\'');
|
|
120
|
+
if quote.is_some() {
|
|
121
|
+
value_start += 1;
|
|
122
|
+
}
|
|
123
|
+
let mut value_end = if let Some(quote) = quote {
|
|
124
|
+
text.as_bytes()
|
|
125
|
+
.get(value_start..)
|
|
126
|
+
.and_then(|tail| tail.iter().position(|byte| *byte == quote))
|
|
127
|
+
.map(|offset| value_start + offset)
|
|
128
|
+
.unwrap_or_else(|| find_secret_end(text, value_start))
|
|
129
|
+
} else {
|
|
130
|
+
find_secret_end(text, value_start)
|
|
131
|
+
};
|
|
132
|
+
if value_end <= value_start {
|
|
133
|
+
search_start = key_end;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if quote.is_none() {
|
|
137
|
+
value_end = value_end
|
|
138
|
+
.min(
|
|
139
|
+
text[value_start..]
|
|
140
|
+
.find(',')
|
|
141
|
+
.map(|offset| value_start + offset)
|
|
142
|
+
.unwrap_or(text.len()),
|
|
143
|
+
)
|
|
144
|
+
.min(
|
|
145
|
+
text[value_start..]
|
|
146
|
+
.find('}')
|
|
147
|
+
.map(|offset| value_start + offset)
|
|
148
|
+
.unwrap_or(text.len()),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
text.replace_range(value_start..value_end, "[REDACTED]");
|
|
152
|
+
count += 1;
|
|
153
|
+
search_start = value_start + "[REDACTED]".len();
|
|
154
|
+
}
|
|
155
|
+
count
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn redact_secret_markers(text: &mut String) -> usize {
|
|
159
|
+
let mut count = 0;
|
|
160
|
+
for marker in ["sk-", "ghp_", "xoxb-"] {
|
|
161
|
+
while let Some(start) = text.find(marker) {
|
|
162
|
+
let end = find_secret_end(text, start);
|
|
163
|
+
text.replace_range(start..end, "[REDACTED]");
|
|
164
|
+
count += 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
count
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn find_secret_end(text: &str, start: usize) -> usize {
|
|
171
|
+
text[start..]
|
|
172
|
+
.char_indices()
|
|
173
|
+
.find_map(|(offset, ch)| {
|
|
174
|
+
if ch.is_whitespace() || matches!(ch, ',' | ';' | ')' | ']' | '}') {
|
|
175
|
+
Some(start + offset)
|
|
176
|
+
} else {
|
|
177
|
+
None
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.unwrap_or(text.len())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[cfg(test)]
|
|
184
|
+
mod tests {
|
|
185
|
+
use super::redact_output;
|
|
186
|
+
use crate::exec::CommandOutput;
|
|
187
|
+
use std::process::Command;
|
|
188
|
+
|
|
189
|
+
fn ok_status() -> std::process::ExitStatus {
|
|
190
|
+
Command::new("sh")
|
|
191
|
+
.arg("-c")
|
|
192
|
+
.arg("exit 0")
|
|
193
|
+
.status()
|
|
194
|
+
.expect("status")
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[test]
|
|
198
|
+
fn redacts_secret_like_stdout_and_stderr() {
|
|
199
|
+
let output = CommandOutput {
|
|
200
|
+
status: ok_status(),
|
|
201
|
+
stdout: b"API_TOKEN=secret-value\nplain\nsk-live123\n".to_vec(),
|
|
202
|
+
stderr: b"Authorization: Bearer bearer-secret\nghp_secret123\n".to_vec(),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
let redacted = redact_output(&output);
|
|
206
|
+
let stdout = String::from_utf8(redacted.output.stdout).expect("stdout utf8");
|
|
207
|
+
let stderr = String::from_utf8(redacted.output.stderr).expect("stderr utf8");
|
|
208
|
+
|
|
209
|
+
assert_eq!(redacted.count, 4);
|
|
210
|
+
assert!(stdout.contains("API_TOKEN=[REDACTED]"));
|
|
211
|
+
assert!(stdout.contains("[REDACTED]"));
|
|
212
|
+
assert!(stderr.contains("Authorization: Bearer [REDACTED]"));
|
|
213
|
+
assert!(!stdout.contains("secret-value"));
|
|
214
|
+
assert!(!stdout.contains("sk-live123"));
|
|
215
|
+
assert!(!stderr.contains("bearer-secret"));
|
|
216
|
+
assert!(!stderr.contains("ghp_secret123"));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[test]
|
|
220
|
+
fn redacts_json_colon_and_repeated_secret_forms() {
|
|
221
|
+
let output = CommandOutput {
|
|
222
|
+
status: ok_status(),
|
|
223
|
+
stdout:
|
|
224
|
+
br#"{"access_token":"tok-1","api_key":"sk-json"} password: hunter2 sk-one sk-two"#
|
|
225
|
+
.to_vec(),
|
|
226
|
+
stderr: b"Authorization: Bearer first Authorization: Bearer second\n".to_vec(),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let redacted = redact_output(&output);
|
|
230
|
+
let stdout = String::from_utf8(redacted.output.stdout).expect("stdout utf8");
|
|
231
|
+
let stderr = String::from_utf8(redacted.output.stderr).expect("stderr utf8");
|
|
232
|
+
|
|
233
|
+
for secret in [
|
|
234
|
+
"tok-1", "sk-json", "hunter2", "sk-one", "sk-two", "first", "second",
|
|
235
|
+
] {
|
|
236
|
+
assert!(!stdout.contains(secret), "stdout leaked {secret}: {stdout}");
|
|
237
|
+
assert!(!stderr.contains(secret), "stderr leaked {secret}: {stderr}");
|
|
238
|
+
}
|
|
239
|
+
assert!(redacted.count >= 7);
|
|
240
|
+
}
|
|
241
|
+
}
|