Redact secrets from app-server command execution items (#36893)

## Why

Client-facing command execution items can include recognizable secrets in the
rendered command or parsed command actions.

## What changed

- Redact secrets in `commandExecution.command` and `commandActions` for live
  items, completed items, and replayed thread history.
- Keep command approval requests backed by the original executable command and
  parsed actions while using the redacted representation for display items.
- Document that execution item commands are redacted display values rather than
  executable commands.

## Testing

- Cover redaction for command conversion, parsed search actions, legacy replay,
  approval flows, completion notifications, and rejected commands.

GitOrigin-RevId: 2ad056f21882bf5166182ad1a4ff0bf6471d9c0e
This commit is contained in:
Alec Barber authored and copyberry committed 2026-08-04 13:16:58 +00:00
1 parent 6d4d9442c7
commit fcf636a41d
10 files changed
+238 -70

No files matched your search

@@ -10,7 +10,7 @@ use super::UserInput;
use super::shared::v2_enum_from_core;
use crate::JsonSchema;
use crate::TS;
use crate::protocol::item_builders::command_actions_for_path_uri;
use crate::protocol::item_builders::CommandExecutionPresentation;
use crate::protocol::item_builders::convert_patch_changes;
use crate::protocol::item_builders::review_output_text;
use codex_experimental_api_macros::ExperimentalApi;
@@ -43,7 +43,6 @@ use codex_protocol::protocol::GuardianUserAuthorization as CoreGuardianUserAutho
use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
use codex_protocol::protocol::ReviewDecision as CoreReviewDecision;
use codex_protocol::protocol::SubAgentActivityKind as CoreSubAgentActivityKind;
use codex_shell_command::parse_command::shlex_join;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::LegacyAppPathString;
use serde::Deserialize;
@@ -841,24 +840,31 @@ impl From<CoreTurnItem> for ThreadItem {
summary: reasoning.summary_text,
content: reasoning.raw_content,
},
CoreTurnItem::CommandExecution(command) => ThreadItem::CommandExecution {
id: command.id,
plugin_id: command.plugin_id,
script_path: command.script_path,
command: shlex_join(&command.command),
cwd: command.cwd.clone().into(),
process_id: command.process_id,
source: command.source.into(),
status: command.status.into(),
command_actions: command_actions_for_path_uri(&command.parsed_cmd, &command.cwd),
aggregated_output: command
.aggregated_output
.filter(|output| !output.is_empty()),
exit_code: command.exit_code,
duration_ms: command
.duration
.and_then(|duration| i64::try_from(duration.as_millis()).ok()),
},
CoreTurnItem::CommandExecution(command) => {
let presentation = CommandExecutionPresentation::from_raw(
&command.command,
&command.parsed_cmd,
&command.cwd,
);
ThreadItem::CommandExecution {
id: command.id,
plugin_id: command.plugin_id,
script_path: command.script_path,
command: presentation.command,
cwd: command.cwd.clone().into(),
process_id: command.process_id,
source: command.source.into(),
status: command.status.into(),
command_actions: presentation.command_actions,
aggregated_output: command
.aggregated_output
.filter(|output| !output.is_empty()),
exit_code: command.exit_code,
duration_ms: command
.duration
.and_then(|duration| i64::try_from(duration.as_millis()).ok()),
}
}
CoreTurnItem::DynamicToolCall(call) => ThreadItem::DynamicToolCall {
id: call.id,
namespace: call.namespace,
@@ -2927,10 +2927,19 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
process_id: Some("pid-1".to_string()),
command: vec!["echo".to_string(), "done".to_string()],
command: vec![
"git".to_string(),
"-c".to_string(),
"http.extraHeader=Authorization: Bearer example_synthetic_bearer_token_123456"
.to_string(),
"-c".to_string(),
"http.extraHeader=X-Trace:example".to_string(),
"push".to_string(),
],
cwd: PathUri::from_abs_path(&test_path_buf("/tmp").abs()),
parsed_cmd: vec![codex_protocol::parse_command::ParsedCommand::Unknown {
cmd: "echo done".to_string(),
cmd: "git -c 'http.extraHeader=Authorization: Bearer example_synthetic_bearer_token_123456' -c http.extraHeader=X-Trace:example push"
.to_string(),
}],
source: CoreExecCommandSource::Agent,
interaction_input: None,
@@ -2949,13 +2958,15 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
command: "echo done".to_string(),
command: "git -c 'http.extraHeader=Authorization: Bearer [REDACTED_SECRET]' -c 'http.extraHeader=X-Trace:example' push"
.to_string(),
cwd: LegacyAppPathString::from_abs_path(&test_path_buf("/tmp").abs()),
process_id: Some("pid-1".to_string()),
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::Completed,
command_actions: vec![CommandAction::Unknown {
command: "echo done".to_string(),
command: "git -c 'http.extraHeader=Authorization: Bearer [REDACTED_SECRET]' -c http.extraHeader=X-Trace:example push"
.to_string(),
}],
aggregated_output: Some("done\n".to_string()),
exit_code: Some(0),