Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f9c29e0
Add agent policy hooks spec
etherman-os May 3, 2026
8a0e2a5
Add agent policy hook preflight scaffold
etherman-os May 3, 2026
edb2261
Wire agent policy hooks into action execution
etherman-os May 3, 2026
d5d6be5
Address agent policy hook review findings
etherman-os May 3, 2026
a7aa47f
Harden agent policy hook lifecycle
etherman-os May 3, 2026
e31aec2
Address policy hook audit findings
etherman-os May 3, 2026
a83865e
Harden policy hook preflight boundaries
etherman-os May 3, 2026
0db91ac
Close policy hook review gaps
etherman-os May 3, 2026
2a693ec
Tighten policy hook cancellation and timeout
etherman-os May 3, 2026
6c09a02
Preserve Warp denials in policy hook preflight
etherman-os May 3, 2026
9cf4c30
Clarify policy preflight preprocessing input borrow
etherman-os May 3, 2026
dc08cc8
Isolate policy hook secrets in disabled configs and stdio
etherman-os May 3, 2026
385cd72
Reject unsafe policy hook profile persistence
etherman-os May 3, 2026
820b0b9
Sanitize unsafe policy hook profile serialization
etherman-os May 3, 2026
0d20e25
Recompose cached policy decisions and redact command credentials
etherman-os May 3, 2026
89e9806
Avoid cached hook autoapproval after permission changes
etherman-os May 3, 2026
718feeb
Create policy audit directory with private permissions
etherman-os May 3, 2026
b5b624a
Scope policy preflight cache by action payload
etherman-os May 3, 2026
f0b856c
Bound policy event payloads and file edit denials
etherman-os May 3, 2026
aa0a1b6
Govern shell writes and preserve policy denials
etherman-os May 3, 2026
d5086da
Restrict hook autoapproval to successful hooks
etherman-os May 3, 2026
667e9fd
Defer file edit preprocessing on policy ask
etherman-os May 3, 2026
9a67599
Preserve file edit confirmation after policy preprocessing
etherman-os May 3, 2026
f81b9cc
Tighten policy hook credential and shell denial handling
etherman-os May 3, 2026
1efb850
Require policy denial markers and split secret redaction
etherman-os May 3, 2026
8ad569b
Tighten policy hook secret validation
etherman-os May 3, 2026
5f271ec
Close remaining policy hook audit gaps
etherman-os May 3, 2026
70e6dea
Harden policy hook restore and MCP redaction
etherman-os May 3, 2026
84ddf35
Preserve file edit policy denials with marker
etherman-os May 3, 2026
6f5802b
Harden policy preflight caching and denials
etherman-os May 3, 2026
d069f47
Validate stdio hook command credentials
etherman-os May 3, 2026
f2a8afb
Use raw actions for policy preflight cache keys
etherman-os May 3, 2026
de1f105
Decode HTTP hook fragments for credential checks
etherman-os May 3, 2026
c30229a
Require per-hook autoapproval opt-in
etherman-os May 4, 2026
830e024
Harden policy hook audit edge cases
etherman-os May 4, 2026
9e6fe17
Inspect stdio hook command fragments
etherman-os May 4, 2026
8c0e9f9
Harden stdio hook credential validation
etherman-os May 4, 2026
6f91a8c
Reject persisted policy hook credential bypasses
etherman-os May 4, 2026
fe01e14
Harden policy hook redaction and autoapproval
etherman-os May 4, 2026
97c11d3
Audit harden policy hook edge cases
etherman-os May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions app/src/ai/agent/api/convert_conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::ai::agent::conversation::{AIConversation, AIConversationId};
use crate::ai::agent::task::TaskId;
use crate::ai::agent::todos::AIAgentTodoList;
use crate::ai::agent::{
decode_command_policy_denied_reason, decode_file_edits_policy_denied_reason,
AIAgentActionResult, AIAgentActionResultType, AIAgentContext, AIAgentExchange,
AIAgentExchangeId, AIAgentInput, AIAgentOutput, AIAgentOutputMessage, AIAgentOutputStatus,
CallMCPToolResult, CancellationReason, CloneRepositoryURL, CreateDocumentsResult,
Expand All @@ -25,10 +26,15 @@ use crate::ai::agent::{
Shared, ShellCommandCompletedTrigger, ShellCommandError, SuggestNewConversationResult,
SuggestPromptResult, TransferShellCommandControlToUserResult, UpdatedFileContext,
UploadArtifactResult, WriteToLongRunningShellCommandResult,
WRITE_TO_SHELL_POLICY_DENIED_COMMAND_ID, WRITE_TO_SHELL_POLICY_DENIED_EXIT_CODE,
WRITE_TO_SHELL_POLICY_DENIED_PREFIX,
};
use crate::ai::block_context::BlockContext;
use crate::ai::document::ai_document_model::{AIDocumentId, AIDocumentVersion};
use crate::ai::llms::LLMId;
use crate::ai::policy_hooks::redaction::{
redact_command_for_policy, redact_sensitive_text_for_policy,
};
use crate::ai_assistant::execution_context::{WarpAiExecutionContext, WarpAiOsContext};
use crate::terminal::model::block::BlockId;
use crate::terminal::model::terminal_model::BlockIndex;
Expand Down Expand Up @@ -591,9 +597,31 @@ pub(crate) fn convert_tool_call_result_to_input(
is_alt_screen_active: snapshot.is_alt_screen_active,
},
Some(api::run_shell_command_result::Result::PermissionDenied(
api::PermissionDenied { .. },
))
| None => {
api::PermissionDenied { reason },
)) => match reason {
Some(api::permission_denied::Reason::DenylistedCommand(())) => {
RequestCommandOutputResult::Denylisted {
command: result.command.clone(),
}
}
None => {
#[allow(deprecated)]
let output = result.output.as_str();
if let Some(reason) = decode_command_policy_denied_reason(output) {
if reason.is_empty() {
RequestCommandOutputResult::CancelledBeforeExecution
} else {
RequestCommandOutputResult::PolicyDenied {
command: redact_command_for_policy(&result.command),
reason: redact_sensitive_text_for_policy(&reason),
}
}
} else {
RequestCommandOutputResult::CancelledBeforeExecution
}
}
},
None => {
// If no result is present, treat as cancelled
RequestCommandOutputResult::CancelledBeforeExecution
}
Expand Down Expand Up @@ -621,11 +649,29 @@ pub(crate) fn convert_tool_call_result_to_input(
},
Some(api::write_to_long_running_shell_command_result::Result::CommandFinished(
finished,
)) => WriteToLongRunningShellCommandResult::CommandFinished {
block_id: finished.command_id.clone().into(),
output: finished.output.clone(),
exit_code: ExitCode::from(finished.exit_code),
},
)) => {
let is_policy_denial_marker =
finished.command_id == WRITE_TO_SHELL_POLICY_DENIED_COMMAND_ID
&& finished.exit_code == WRITE_TO_SHELL_POLICY_DENIED_EXIT_CODE;
let policy_denial_reason = if is_policy_denial_marker {
finished
.output
.strip_prefix(WRITE_TO_SHELL_POLICY_DENIED_PREFIX)
} else {
None
};
if let Some(reason) = policy_denial_reason {
WriteToLongRunningShellCommandResult::PolicyDenied {
reason: redact_sensitive_text_for_policy(reason),
}
} else {
WriteToLongRunningShellCommandResult::CommandFinished {
block_id: finished.command_id.clone().into(),
output: finished.output.clone(),
exit_code: ExitCode::from(finished.exit_code),
}
}
}
Some(api::write_to_long_running_shell_command_result::Result::Error(api::ShellCommandError{
r#type: Some(api::shell_command_error::Type::CommandNotFound(()))
})) => WriteToLongRunningShellCommandResult::Error(ShellCommandError::BlockNotFound),
Expand Down Expand Up @@ -760,8 +806,14 @@ pub(crate) fn convert_tool_call_result_to_input(
}
}
Some(api::apply_file_diffs_result::Result::Error(error)) => {
RequestFileEditsResult::DiffApplicationFailed {
error: error.message.clone(),
if let Some(reason) = decode_file_edits_policy_denied_reason(&error.message) {
RequestFileEditsResult::PolicyDenied {
reason: redact_sensitive_text_for_policy(&reason),
}
} else {
RequestFileEditsResult::DiffApplicationFailed {
error: error.message.clone(),
}
}
}
None => RequestFileEditsResult::Cancelled,
Expand Down
Loading