PreToolUse denial appends raw command or patch after sanitized hook reason
Summary
On Codex CLI 0.144.1 for Windows, a PreToolUse hook can correctly deny a shell command or patch with a sanitized permissionDecisionReason, but the runtime then appends the original raw command or patch to the visible denial message.
This defeats hook-side redaction. It can expose a sensitive path, dummy marker, command argument, or patch body even when the hook deliberately returns only a reason code and a 12-character SHA-256 fingerprint.
A related routing problem for function-call apply_patch is tracked in #26751. This report focuses on the end-to-end denial display and raw-input disclosure.
Environment
- Codex CLI:
0.144.1 - Platform: Windows 11, PowerShell
- Hook event:
PreToolUse - Affected tool paths observed: shell and
functions.apply_patch
Hook response
The hook exits successfully and returns only sanitized JSON:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "reason=secret-read.shell; command_hash=0123456789ab"
}
}
The response contains no raw command, path, or patch text.
Observed behavior
Codex blocks before execution, but the visible result has this shape:
Command blocked by PreToolUse hook: reason=...; command_hash=.... Command: <original raw command or patch>
For functions.apply_patch, the hook can also receive an input shape it does not recognize and return reason=missing-path; Codex then appends the full raw patch. That routing aspect appears related to #26751.
Expected behavior
When a PreToolUse hook denies a tool call:
- The tool must remain blocked before execution.
- The visible result should contain the sanitized denial reason supplied by the hook.
- The original tool arguments must not be appended automatically, or there must be a documented hook/config option that suppresses them end to end.
- The behavior should be consistent for shell, custom/freeform
apply_patch, and function-callfunctions.apply_patch.
Reproduction
- Configure a
PreToolUsehook for shell or edit tools. - Return
permissionDecision=denywith a reason code and a fingerprint only. - Attempt a harmless read of a nonexistent dummy secret path, or a harmless patch targeting a nonexistent dummy protected path.
- Observe that execution is blocked.
- Observe that Codex appends the original raw command or patch after the sanitized reason.
No real secret or credential is needed to reproduce this.
Verification performed
- The hook output was tested directly and contains only the sanitized reason and fingerprint.
- The live tool call was blocked before execution.
- The nonexistent dummy file was not created or read.
- Repeated local hook tests pass.
- The disclosure appears only after the hook response reaches the Codex runtime/UI layer.
Security impact
Hook authors cannot guarantee that a deny response redacts sensitive tool input from the user-visible transcript. A security hook can prevent execution while still allowing the runtime to disclose the exact input that triggered the block.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗