Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions plugins/codex/hooks/block-direct-codex-cli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
# PreToolUse hook: block direct "codex" CLI invocations and redirect to the plugin.

# jq is expected but not a hard dependency — pass through if unavailable.
command -v jq &>/dev/null || exit 0

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail closed when JSON parser is unavailable

The hook now depends on jq to extract .tool_input.command, but if jq is missing (not guaranteed by this repo’s documented requirements), this assignment fails and leaves COMMAND empty, after which the script returns success at the end. In that environment, direct codex invocations are silently not blocked, so the new protection does not work. Use a guaranteed runtime (e.g., Node) for parsing or explicitly error/block when parsing fails.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a jq availability check, but it exits 0 (allow) rather than exit 2 (block) when jq is missing. Fail-closed would block every Bash call on systems without jq, which is a worse outcome than missing the codex guard. jq is expected to be present on any system running Codex CLI and Node.js, but we don't want to introduce it as a hard dependency.


# Match "codex" at a command position: start of string (with optional leading
# whitespace) or after a shell separator. This avoids false positives when "codex"
# appears inside arguments like commit messages or strings.
if echo "$COMMAND" | grep -qE '(^[[:space:]]*|[;&|][[:space:]]*)codex([[:space:]]|$)'; then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match codex after assignment prefixes

The PreToolUse guard still allows direct Codex CLI calls when the command starts with shell variable assignments (for example, FOO=1 codex exec ...), because the regex only matches codex at the start of the string or after [;&|]. That leaves an easy bypass for the protection this hook is intended to enforce. Fresh evidence: with this commit’s script, piping {"tool_input":{"command":"FOO=1 codex exec \"hi\""}} into the hook exits 0 instead of 2.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged but accepting this edge case. The broader word-boundary regex that would catch FOO=1 codex causes false positives on commands containing "codex" in string arguments (e.g. commit messages). Claude Code does not generate env-var-prefixed command forms, so the positional approach is the right trade-off.

echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2
exit 2
fi

exit 0
12 changes: 12 additions & 0 deletions plugins/codex/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
{
"description": "Optional stop-time review gate for Codex Companion.",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/block-direct-codex-cli.sh\"",
"timeout": 5
}
]
}
],
"SessionStart": [
{
"hooks": [
Expand Down