openai / openai/codex

Hooks: mediate non-empty write_stdin before terminal input delivery

Open
#46,372 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI hooks tool-calls
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Version and environment

Codex CLI 0.154.0, Linux. Deterministic loopback Responses provider with a fixture model catalog; no model inference, credentials, or production data. Both direct tool calls and calls nested through code mode were exercised.

Problem

Non-empty write_stdin delivers bytes to a previously authorized exec_command process without invoking PreToolUse again. A hook that authorized process creation cannot inspect or deny later input. This is a missing enforcement point, not a claim that hooks provide complete sandbox confinement.

Reproduction and observed result

The reproducible harness and report are pinned here:

From that Guardrail revision, with Codex 0.154.0 installed:

go build -o /tmp/guardrail-codex ./cmd/guardrail
python3 test/smoke/codex_probe.py /tmp/guardrail-codex --mediation
python3 test/smoke/codex_probe.py /tmp/guardrail-codex --mediation --code-mode

The fixture starts a harmless cat > stdin.txt terminal, sends fixture bytes, polls, sends EOF, and applies a harmless patch. It verifies actual file contents and process termination, not just tool response strings.

Action Observed pre-hook
Start process with exec_command Yes, canonical Bash
Send non-empty input with write_stdin No; bytes reach the output file
Poll with empty input No
Send EOF No; process exits
Apply patch Yes

Both baseline runs pass with exactly two pre-hooks (Bash and patch). A passing baseline means the gap was reproduced, not that stdin is protected. All fixture state is disposable. Hook trust is explicitly configured by the harness.

Expected behavior

Offer a synchronous PreToolUse checkpoint for non-empty terminal input before any bytes or control characters are delivered. Include immutable target-process provenance (session/process ID, original exec call ID, original command and effective execution directory). A deny must deliver no bytes. If rewrites are supported, they must not retarget the terminal or change provenance; native stdin approval should see the rewritten bytes. Empty polls can remain distinct and unhooked.

Arbitrary terminal input is not necessarily shell syntax, so projecting it as a fresh Bash command would be incorrect. Process identity should be revalidated around any hook/approval wait.

Additional measurement and limitations

At 2026-09-18 05:16:14 UTC our ordinary Guardrail audit file contained 60,925 valid records, zero Codex-tagged records and zero Codex unknown-native-tool hits (record interval: 2026-09-04 03:04:16 UTC through 2026-09-18 05:09:27 UTC). This is an observability measurement, not causal proof: no events alone cannot distinguish an unused plane, untrusted/disabled hooks, another audit destination, or skipped dispatch. The isolated fixture above is the evidence that stdin executes without its own pre-hook.

The shell_tool=false restriction removes both initial exec and stdin, including code-mode calls, but also removes ordinary command work. We are validating a process-manager checkpoint patch against upstream base 7498521d288b9b3b96ffba4eedf089d8d6e06a84; new direct/nested deny-and-rewrite tests pass locally, while broader validation is ongoing. It is not shipped upstream.

Related but different: #43184 concerns terminal completion events; #32505 concerns code-mode provenance; #18491 tracks broader hook coverage. This report concerns pre-delivery mediation of subsequent terminal input.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the mediation probe report and run test/smoke/codex_probe.py using the commands in the issue, then inspect the process-manager checkpoint patch against upstream base 7498521d288b9b3b96ffba4eedf089d8d6e06a84. Done means non-empty write_stdin input is checked before delivery, denial sends no bytes, rewrites preserve target provenance, and empty polls remain distinct.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.