Add agent policy hooks enforcement#9957
Add agent policy hooks enforcement#9957etherman-os wants to merge 26 commits intowarpdotdev:masterfrom
Conversation
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds host-enforced agent policy hooks across command, file, and MCP action surfaces, plus specs and audit/event plumbing. The enforcement direction is sound, but several implementation details need fixes before merge.
Concerns
- Hook stdout/stderr and HTTP response bodies are buffered before size checks, so the advertised bounds do not prevent memory exhaustion.
- Stdio hook stderr is included in policy failure details without secret redaction.
- Write-file policy events report zero additions/deletions, which can mislead external policy decisions.
- API conversion collapses policy denials into denylist results and drops the policy reason.
Security
- A faulty or malicious configured hook can return unbounded stdio/HTTP output and exhaust memory before the current caps are checked.
- Hook stderr can contain secrets from configured env values and is propagated after truncation but without redaction.
Verdict
Found: 0 critical, 5 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
|
|
||
| let timeout = Duration::from_millis(self.config.hook_timeout_ms(hook)); | ||
| let output = child | ||
| .output() |
There was a problem hiding this comment.
child.output() buffers all stdout and stderr before MAX_HOOK_STDOUT_BYTES is checked, so a noisy hook can exhaust memory; read the pipes with an enforced byte cap and kill the child when the cap is exceeded.
| detail: format!( | ||
| "policy hook exited with {}; stderr={}", | ||
| output.status, | ||
| truncate_for_policy(stderr.trim()) |
There was a problem hiding this comment.
| } | ||
|
|
||
| let response_bytes = response | ||
| .bytes() |
There was a problem hiding this comment.
response.bytes() buffers the entire HTTP response before the size check, so a remote hook can still cause unbounded memory use; stream the body with a hard cap or reject by content_length before buffering.
| .collect::<Vec<_>>(); | ||
| let diff_stats = PolicyDiffStats { | ||
| files_changed: paths.len(), | ||
| additions: 0, |
There was a problem hiding this comment.
| } | ||
| RequestCommandOutputResult::Denylisted { command } => | ||
| RequestCommandOutputResult::Denylisted { command } | ||
| | RequestCommandOutputResult::PolicyDenied { command, .. } => |
There was a problem hiding this comment.
PolicyDenied as a denylist result drops the hook reason and mislabels the denial for API clients; add/preserve a policy-denied result so agents can distinguish host policy blocks and avoid blind retries.
There was a problem hiding this comment.
Overview
This PR adds host-enforced agent policy hooks across command, file, and MCP action surfaces, plus specs and audit/event plumbing. The enforcement direction is sound, but several implementation details need fixes before merge.
Concerns
- Hook stdout/stderr and HTTP response bodies are buffered before size checks, so the advertised bounds do not prevent memory exhaustion.
- Stdio hook stderr is included in policy failure details without secret redaction.
- Write-file policy events report zero additions/deletions, which can mislead external policy decisions.
- API conversion collapses policy denials into denylist results and drops the policy reason.
Security
- A faulty or malicious configured hook can return unbounded stdio/HTTP output and exhaust memory before the current caps are checked.
- Hook stderr can contain secrets from configured env values and is propagated after truncation but without redaction.
Verdict
Found: 0 critical, 5 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
|
|
||
| let timeout = Duration::from_millis(self.config.hook_timeout_ms(hook)); | ||
| let output = child | ||
| .output() |
There was a problem hiding this comment.
child.output() buffers all stdout and stderr before MAX_HOOK_STDOUT_BYTES is checked, so a noisy hook can exhaust memory; read the pipes with an enforced byte cap and kill the child when the cap is exceeded.
| detail: format!( | ||
| "policy hook exited with {}; stderr={}", | ||
| output.status, | ||
| truncate_for_policy(stderr.trim()) |
There was a problem hiding this comment.
| } | ||
|
|
||
| let response_bytes = response | ||
| .bytes() |
There was a problem hiding this comment.
response.bytes() buffers the entire HTTP response before the size check, so a remote hook can still cause unbounded memory use; stream the body with a hard cap or reject by content_length before buffering.
| .collect::<Vec<_>>(); | ||
| let diff_stats = PolicyDiffStats { | ||
| files_changed: paths.len(), | ||
| additions: 0, |
There was a problem hiding this comment.
| } | ||
| RequestCommandOutputResult::Denylisted { command } => | ||
| RequestCommandOutputResult::Denylisted { command } | ||
| | RequestCommandOutputResult::PolicyDenied { command, .. } => |
There was a problem hiding this comment.
PolicyDenied as a denylist result drops the hook reason and mislabels the denial for API clients; add/preserve a policy-denied result so agents can distinguish host policy blocks and avoid blind retries.
|
Addressed the Oz review findings in d5d6be5:
Validation run locally:
Note: /oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds host-enforced Agent Policy Hooks across shell, file, MCP, conversion, and audit paths, plus product and technical specs. The implementation is close, but the preflight lifecycle and hook I/O handling have correctness/security issues that should be fixed before merge.
Concerns
- Cached
askdecisions are consumed before user confirmation, causing confirmed actions to run a second hook preflight with a new event id and duplicate external side effects/audit entries. - Stdio hook timeouts do not cover stdin writes, so a hook that stops reading can leave the governed action stuck indefinitely.
Security
- Hook-provided reason strings are propagated into audit/conversation surfaces without redaction or truncation.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| return None; | ||
| } | ||
|
|
||
| if let Some(decision) = self.completed_policy_preflights.remove(&action.id) { |
There was a problem hiding this comment.
Ask decision here means the subsequent user-confirmed retry starts a brand-new hook preflight with a new event id, so an ask result produces duplicate hook calls/audit records and can re-trigger external approval flows. Keep the decision cached through confirmation (or mark it confirmed) until the action executes/cancels.
| }); | ||
| }; | ||
|
|
||
| stdin |
There was a problem hiding this comment.
write_all indefinitely and leave the action stuck. Wrap the entire spawn/write/read/wait sequence in the configured timeout (or separately time-bound stdin writes) and kill the child on expiry.
| Self { | ||
| hook_name: hook_name.into(), | ||
| decision: response.decision, | ||
| reason: response.reason, |
There was a problem hiding this comment.
response.reason before persisting it.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
|
Addressed the second Oz review in a7aa47f:
Additional local audit pass focused on policy-hook ingress/egress surfaces: hook stdout/stderr, HTTP response bodies, hook reasons, external audit ids, unavailable details, API conversion, conversation YAML, SDK output, and preflight cache cleanup/cancellation paths. Validation run locally after this commit:
Dependency advisory tools are still unavailable in this environment:
/oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds agent policy hook configuration, event/decision plumbing, hook execution transports, audit logging, and executor preflight enforcement for governed agent actions.
Concerns
- Remote HTTP hooks use the default reqwest redirect policy, which can bypass the HTTPS/localhost validation after the initial URL check.
- Command redaction only captures unquoted whitespace-free assignment values, so quoted secret values can be partially sent to hooks and audit records.
Security
- The redirect behavior can downgrade or reroute policy event delivery, including configured hook headers, outside the validated endpoint constraints.
- The redaction regex can leak parts of quoted secret assignments in command payloads.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| detail: format!("failed to serialize policy event: {source}"), | ||
| })?; | ||
|
|
||
| let client = reqwest::Client::new(); |
There was a problem hiding this comment.
Client::new() follows redirects by default, so an HTTPS hook can redirect to plaintext/non-local HTTP and bypass the URL validation. Disable redirects or validate every redirect target before sending the policy event and configured headers.
|
|
||
| static SECRET_ASSIGNMENT_RE: Lazy<Regex> = Lazy::new(|| { | ||
| Regex::new( | ||
| r"(?i)\b([A-Z0-9_.-]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API[_-]?KEY|ACCESS[_-]?KEY)[A-Z0-9_.-]*)=([^\s;&|]+)", |
There was a problem hiding this comment.
PASSWORD='foo bar' becomes PASSWORD=<redacted> bar' before being sent to hooks/audit. Redact complete quoted assignment values or use shell-aware parsing.
|
Follow-up for the latest Oz findings, pushed in
Verification run locally:
/oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
This comment was marked as spam.
This comment was marked as spam.
There was a problem hiding this comment.
Overview
This PR adds policy hook configuration, event/decision plumbing, hook execution, audit output, and enforcement integration for shell, file, and MCP agent actions.
Concerns
- Policy preflight is invoked from
try_to_execute_action, afterRequestFileEditspreprocessing has already generated candidate diffs and read file contents, so a deny cannot prevent that governed work from starting. - The configured hook autoapproval path returns
Allowedbut then falls through to the existingneeds_confirmationcheck, so it still prompts instead of executing.
Security
- Hook env/header secret values serialize transparently into the execution profile, which is a synced JSON cloud object; configured tokens can be uploaded in raw form.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| || can_auto_execute | ||
| || (is_agent_autonomous && action.action.is_request_command_output())); | ||
| #[cfg(not(target_family = "wasm"))] | ||
| if let Some(preflight_state) = self.start_policy_preflight_if_needed( |
There was a problem hiding this comment.
queue_actions has awaited executor preprocessing; RequestFileEdits::preprocess_action already applies/builds candidate diffs and reads file contents before a hook denial can happen. Move policy preflight before governed preprocessing, or defer diff preprocessing until the hook allows/asks is confirmed.
|
|
||
| return TryExecuteResult::ExecutedSync; | ||
| } | ||
| PolicyPreflightState::Allowed => {} |
There was a problem hiding this comment.
allow with allow_hook_autoapproval does not autoapprove because this branch falls through to the original if needs_confirmation block below. Carry an autoapproval override or skip that confirmation branch when the effective policy decision explicitly allowed it.
| } | ||
|
|
||
| #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
| #[serde(transparent)] |
There was a problem hiding this comment.
#[serde(transparent)] serializes hook env/header secrets as raw strings, and execution profiles are synced JSON cloud objects. Store secret references or keep these values in a private local secret store instead of embedding tokens in the profile payload.
|
Follow-up for the latest Oz review, pushed in
Verification run locally:
|
|
/oz-review |
There was a problem hiding this comment.
Overview
This PR adds the agent policy hook configuration, event schema, hook engine, audit logging, and action-executor enforcement paths. The implementation covers the main governed surfaces, but I found two correctness issues that should be fixed before merge.
Concerns
- Policy hooks are bypassed for long-running shell writes because unsupported action kinds fall through without starting a preflight.
- File-edit policy denials lose the new stable
PolicyDeniedresult when converted through the API shape, so reloaded/round-tripped results become generic diff failures.
Security
- Long-running shell writes can send arbitrary text to an existing shell/REPL without invoking the configured host policy hook, leaving a bypass around the intended shell-action enforcement.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| } => Some(AgentPolicyAction::ReadMcpResource( | ||
| PolicyReadMcpResourceAction::new(*server_id, name.clone(), uri.clone()), | ||
| )), | ||
| _ => None, |
There was a problem hiding this comment.
WriteToLongRunningShellCommand and other shell-write actions fall through here, so configured hooks are never run before text is sent to an existing shell/REPL. Add policy coverage for PTY writes or explicitly deny them while hooks are active so shell execution cannot bypass host policy.
| }, | ||
| ), | ||
| ), | ||
| RequestFileEditsResult::PolicyDenied { reason } => Ok( |
There was a problem hiding this comment.
PolicyDenied to a generic ApplyFileDiffs::Error loses the stable policy-denied variant on API/persistence round trips; the loader maps this back to DiffApplicationFailed. Preserve a machine-readable policy denial, or add inverse parsing like the shell-command path, so agents and restored conversations do not treat host-policy blocks as diff failures.
|
/oz-review Latest fixes pushed in
Verification:
|
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
@etherman-os I just reported them as spam! |
There was a problem hiding this comment.
Overview
This PR adds product/tech specs plus host-enforced Agent Policy Hooks for shell commands, file operations, MCP calls, hook transports, redaction, auditing, and result plumbing.
Security
- Hook autoapproval can currently treat fail-open/unavailable fallback decisions as successful hook approval, allowing a timed-out or malformed hook to bypass Warp confirmation when
on_unavailable: allowand autoapproval are configured. - Empty hook lists are treated as autoapproval-capable during cached recomposition because
Iterator::allreturns true for an empty iterator, which compounds invalid fail-open configs.
Concerns
- See inline comments for the two autoapproval bypass paths that should be fixed before merge.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| warp_permission, | ||
| hook_results, | ||
| }, | ||
| WarpPermissionDecisionKind::Ask if allow_hook_autoapproval && !hook_results.is_empty() => { |
There was a problem hiding this comment.
Ask for any non-empty allow result set, including synthetic Allow evaluations produced by timeout/malformed/unavailable fallback (error: Some(_)). With on_unavailable: allow, an unavailable hook can bypass the confirmation UI; only autoapprove when the allow came from successful hook responses.
|
|
||
| pub(crate) fn allow_autoapproval_for_all_hooks(&self) -> bool { | ||
| self.allow_hook_autoapproval | ||
| || self |
There was a problem hiding this comment.
Iterator::all returns true for an empty hook list, so an enabled-but-invalid config can be treated as autoapproval-capable during cached recomposition. Require at least one configured hook before applying per-hook autoapproval.
|
/oz-review Latest fixes pushed in
Verification:
|
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds profile-scoped agent policy hooks, policy-event schemas, async preflight enforcement, audit records, and policy-denied result plumbing for shell, file, and MCP actions.
Concerns
- File-edit hook
askdecisions still allow diff preprocessing to run before the user confirms, which can read original file contents and prepare candidate diffs before the policy prompt is shown.
Security
- Stdio hook arguments can persist secrets and expose them through process argv.
- HTTP hook URLs reject userinfo but still allow token-like query or fragment credentials to be persisted in the profile.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| return; | ||
| } | ||
|
|
||
| if denied { |
There was a problem hiding this comment.
ask still falls through to request_file_edits_executor.preprocess_action, which applies candidate diffs and loads original file contents before the user sees the policy confirmation; defer diff preprocessing until after confirmation.
| Stdio { | ||
| command: String, | ||
| #[serde(default)] | ||
| args: Vec<String>, |
There was a problem hiding this comment.
args are serialized in the profile and later passed as process argv, so hook credentials placed here are persisted and exposed in process listings; reject credential-looking args or require secrets to use the env map.
| match self { | ||
| Self::Stdio { env, .. } => validate_secret_value_map(env)?, | ||
| Self::Http { url, headers } => { | ||
| if http_url_contains_credentials(url) { |
There was a problem hiding this comment.
https://policy.example/hook?token=... is still accepted and persisted; reject credential-looking query/fragment values or require credentials through header env refs.
@etherman-os and one of our repo owners has now blocked them |
|
/oz-review Latest fixes pushed in
Verification:
|
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds specs and a host-side Agent Policy Hooks implementation across shell, file, and MCP action surfaces.
Concerns
- File-edit actions that pass policy but need deferred preprocessing can lose the prior user confirmation or hook autoapproval on retry, causing the same action to block again instead of executing.
Verdict
Found: 0 critical, 1 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| .remove(&(conversation_id, action.id.clone())) | ||
| { | ||
| return Some(PolicyPreflightState::Allowed { | ||
| skip_confirmation: false, |
There was a problem hiding this comment.
start_request_file_edits_preprocess_if_needed returns NotReady, then the retry is no longer user-initiated; with skip_confirmation: false, the later needs_confirmation branch blocks on the same confirmation again instead of executing. Preserve the confirmed/autoapproved state through the preprocessing retry.
| skip_confirmation: false, | |
| skip_confirmation: true, |
|
/oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds specs and a host-side Agent Policy Hooks implementation covering profile config, async action preflight, stdio/HTTP transports, redacted policy events, audit logging, and policy-denied result plumbing.
Concerns
- [SECURITY] Stdio hook arg validation can still persist raw secrets supplied as split flag/value args, so credential-bearing profile JSON can slip through validation.
- Long-running shell write result conversion now treats every unlabeled shell write error as a host-policy denial, which can misclassify older or generic API payloads after reload/session-sharing.
Security
- Stdio hook configuration needs adjacent-argument credential detection before persistence.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| } | ||
|
|
||
| fn validate_stdio_args(args: &[String]) -> Result<(), AgentPolicyHookConfigError> { | ||
| if args.iter().any(|arg| stdio_arg_contains_credentials(arg)) { |
There was a problem hiding this comment.
["--token", "secret"] or ["--authorization", "Bearer ..."]. Track sensitive option names across adjacent args and reject non-env-reference values there too.
| r#type: Some(api::shell_command_error::Type::CommandNotFound(())) | ||
| })) => WriteToLongRunningShellCommandResult::Error(ShellCommandError::BlockNotFound), | ||
| Some(api::write_to_long_running_shell_command_result::Result::Error(_)) | None => WriteToLongRunningShellCommandResult::Cancelled, | ||
| Some(api::write_to_long_running_shell_command_result::Result::Error(api::ShellCommandError{ |
There was a problem hiding this comment.
ShellCommandError { type: None } as a policy denial. Older or generic unlabeled errors were previously cancelled, so API reload/session-sharing can now report unrelated failures as host-policy blocks; add an explicit policy marker/reason before mapping this to PolicyDenied.
|
@oz-for-oss /oz-review Latest fixes pushed in
Verification:
|
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds Agent Policy Hooks across Warp-owned agent action surfaces, including profile configuration, async preflight execution, policy decision composition, API round-tripping, audit logging, and redaction.
Concerns
- Command-result reload logic can misclassify legacy or generic permission-denied command results as host-policy denials when the expected marker prefix is absent.
Security
- Command redaction for policy hook and audit payloads misses common split-argument secret forms, which can expose model-proposed credentials to external hooks and local audit logs.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| let output = result.output.as_str(); | ||
| let reason = output | ||
| .strip_prefix("Command blocked by host policy: ") | ||
| .unwrap_or(output); |
There was a problem hiding this comment.
PermissionDenied { reason: None } with non-empty output as a host-policy denial. Require the Command blocked by host policy: prefix before producing PolicyDenied; otherwise older or generic permission-denied results will be reclassified after reload and fed back to the agent as policy blocks.
| redact_sensitive_text_for_policy(command) | ||
| } | ||
|
|
||
| pub(crate) fn redact_sensitive_text_for_policy(value: &str) -> String { |
There was a problem hiding this comment.
--token secret, --password secret, or --api-key secret, so those values can be sent to external hooks and written to audit JSONL. Add split-argument credential redaction before serializing command actions.
|
Thanks for the repeated reviews. This ended up being broader than I expected, and I did not anticipate how many edge cases this policy-hook surface would expose. I am going to pause further re-review requests for now and do a more comprehensive local audit/test pass before asking for another review. Apologies for the review noise. |
|
/oz-review Final local audit pass completed after pausing repeated review requests. Latest fixes pushed in
Verification after the latest changes:
I will avoid further re-review requests unless there is a new code change backed by a local audit/test pass. |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds host-enforced Agent Policy Hooks across shell, file, and MCP action surfaces, along with config validation, redaction, audit writing, API conversion, and specs.
Concerns
- HTTP hook URL validation still permits raw token-shaped secrets in query or fragment values when the key is not credential-looking.
- The new stdio environment isolation test depends on
HOME, and the PR's own validation notes that this fails in the full suite when the environment is cleared.
Security
- The URL credential validation should reject common raw token patterns in query/fragment values before profile JSON can persist them.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| return !parsed.username().is_empty() | ||
| || parsed.password().is_some() | ||
| || parsed.query_pairs().any(|(key, value)| { | ||
| text_contains_credentials(&key) || text_contains_credentials(&value) |
There was a problem hiding this comment.
text_contains_credentials only checks credential keywords; reject common token patterns here too so configs like ?q=sk-... cannot persist secrets in profile JSON.
| #[cfg(all(unix, not(target_family = "wasm")))] | ||
| #[tokio::test] | ||
| async fn stdio_engine_does_not_inherit_parent_environment() { | ||
| std::env::var("HOME").expect("HOME must be set for policy hook environment inheritance test"); |
There was a problem hiding this comment.
HOME, and the PR notes it fails in the full suite when the environment is cleared; use a test-scoped parent env var and assert the hook does not inherit that instead.
|
Pushed What changed:
Validation run locally:
I am holding off on another automated review trigger from this comment while I keep the PR quiet unless another review is explicitly requested. |
Description
Adds the product/tech spec plus a working Agent Policy Hooks implementation. Hooks are disabled by default in Agent Profiles, but when configured they run as host-side preflight before governed Warp-owned Agent actions execute.
The implementation covers shell command execution, writes to existing long-running shell commands, file reads, file write/code diff requests, MCP tool calls, and MCP resource reads. Warp remains the enforcement point: hook denials return policy-blocked action results before the underlying command, shell write, file operation, or MCP call starts. Run-to-completion decisions still pass through policy hooks.
Implemented in code
agent_policy_hooksfield onAIExecutionProfile.--token secret,--client-secret secret,--refresh-token secret,--authorization "Bearer ...", andX-API-Key: secret, and rejection of partial literal credential bypasses such assecret$TOKENwhile still allowing pure env references such as$TOKEN,${TOKEN},Bearer $TOKEN, orX-API-Key: $TOKEN.{ "env": "sk-..." }or{ "env": "Bearer ..." }are rejected and unsafe profile serialization falls back to the default disabled config.warp.agent_policy_hook.v1event/response schema with redacted command payloads, redacted long-running shell write payloads, bounded path and MCP argument-key collections with omitted counts, and MCP argument keys only.askdecisions defer diff preprocessing and original-file reads until after the user confirms the policy prompt; confirmed or hook-autoapproved file edits preprocess and then retry execution without dropping the prior confirmation/autoapproval state.BlocklistAIActionExecutorwith pending decision cache scoped by conversation id, action id, and redacted action payload; cancellation cleanup; retry event wiring; and cached hook-result recomposition against the current Warp permission snapshot on retry.0700), private audit file creation (0600), redacted action payload, hook results, effective decision, error class, reason, and external audit id.RequestFileEditsResult::PolicyDeniedrather than a generic diff-application failure.askreason is carried into the blocked conversation status while preserving the existing confirmation flow.Security notes
RequestFileEditsResult::PolicyDeniedrather than becoming generic diff failures.askdecisions do not apply candidate diffs or load original file contents before the user sees and confirms the policy prompt.envsecret-reference map; credential-looking literal args such as--token=...,--token ...,--client-secret ...,--refresh-token ...,--authorization "Bearer ...",API_KEY=...,clientSecret=...,Authorization: Bearer ..., split header forms such asX-API-Key: ..., partial literal/env hybrids such assecret$TOKEN, or bearer/basic literal fragments are rejected before validation/persistence.?token=...,?api_key=...,?clientSecret=...,?accessToken=...,?refresh-token=...,#access_token=..., and raw token-shaped values such as?q=sk-...or#state=ghp_...are rejected before validation/persistence.http:/https:values with userinfo-like authority or token-shaped query/fragment suffixes.askeven ifon_unavailable: allowis configured.--token secret,--password secret,--api-key secret,--client-secret secret,--refresh-token secret,--access-token secret,--clientSecret=secret,--authorization Bearer ..., andcurl -u/--user/--proxy-usercredentials before hook/audit/API payloads are serialized.AgentPolicyHookConfigserialization runs safe-to-persist checks and sanitizes unsafe config to the default disabled config instead of panicking, so disabled/inactive hook config with persisted credentials cannot be written into local or cloud-synced execution profile JSON.http:/https:values with userinfo-like authority or query/fragment credentials are rejected before profile storage.ask; the completed hook reason must be shown in the confirmation flow.cargo auditandcargo deny check advisorieswere run. Both fail on existingCargo.lockbaseline advisories; this PR does not modifyCargo.tomlorCargo.lock. Current reported baseline items includerustls-webpkiRUSTSEC-2026-0098/0099/0104, local crate-name collision onwarpRUSTSEC-2022-0082,websocketRUSTSEC-2022-0035, plus existingdiesel,rand,bincode,core2, andsafememwarnings/advisories.Follow-ups
Linked Issue
N/A - no active linked issue. This PR is a standalone implementation.
Screenshots / Videos
N/A - no new visual UI surface.
Testing
cargo fmt --all --checkgit diff --checkcargo test -p ai --lib(157 passed)CARGO_BUILD_JOBS=1 cargo test -p warp policy_hooks --lib(61 passed)CARGO_BUILD_JOBS=1 cargo test -p warp redact_inputs_redacts_policy_denied_command_result_command --lib(1 passed)CARGO_BUILD_JOBS=1 cargo test -p warp test_convert_tool_call_result_to_input --lib(10 passed)CARGO_BUILD_JOBS=1 cargo check -p warp --features agent_mode_evals --libCARGO_BUILD_JOBS=1 cargo test -p warp terminal::model::grid::grid_handler::secrets::tests::test_bytes_processed_for_secrets_after_turning_redaction --lib -- --nocapture(4 passed; isolates the secret-redaction tests that fail only in the full-suite order/environment)CARGO_BUILD_JOBS=1 cargo test -p warp --lib(3801 passed, 13 failed, 7 ignored. Allai::policy_hookstests pass in the full suite, includingstdio_engine_does_not_inherit_parent_environment. Remaining failures are outside this PR surface in this local environment: telemetry/settings/bootstrap/terminal view/input/workspace tests; the secret-redaction failures pass when isolated as noted above.)cargo audit(run; fails on existingCargo.lockbaseline advisories, no dependency file changes in this PR; 5 vulnerabilities and 15 allowed warnings reported)cargo deny check advisories(run; fails on existingCargo.lockbaseline advisories, no dependency file changes in this PR; reporteddiesel,rand, andrustls-webpkiadvisories)Agent Mode