From 07c430b7ce81c180a27fcd88c36a5b33d151a3ae Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Wed, 8 Apr 2026 22:45:36 +0200 Subject: [PATCH 1/3] release(v0.2.0): hook fail-open hardening + uninstall cleanup helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1545 Direction 3 — Hook fail-open audit - scripts/pre-tool-check.sh now distinguishes curl exit code (network failure) from HTTP success with body (potentially an error response) - Fail-closed (block, exit 2) only on operator-fixable JSON-RPC errors: -32001 auth failure -32601 method not found (plugin/agent version mismatch) -32602 invalid params (plugin bug) - Fail-open (allow, exit 0) on everything else: timeouts, DNS failures, connection refused, TCP reset, HTTP 5xx, JSON-RPC -32603 internal errors, and JSON-RPC -32700 parse errors - Rationale: transient governance infrastructure issues should never block legitimate dev workflows. Only operator-fixable broken configurations should fail closed. Uninstall cleanup helper - scripts/uninstall.sh cleans up the plugin cache directory that Codex CLI's built-in /plugins uninstall leaves behind (known CLI behavior, not a plugin bug — Codex treats local-source plugins as persistent on-disk sources). Supports --dry-run. - Reports AxonFlow references in config.toml and hooks.json without modifying them (user-owned configuration). Version bump .codex-plugin/plugin.json: 0.1.0 → 0.2.0 --- .codex-plugin/plugin.json | 2 +- scripts/pre-tool-check.sh | 54 +++++++++++++++++++++++-------- scripts/uninstall.sh | 68 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 14 deletions(-) create mode 100755 scripts/uninstall.sh diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index aeb7bcb..a09a11e 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "axonflow", "displayName": "AxonFlow Governance", "description": "Policy enforcement, PII detection, and audit trails for OpenAI Codex. Hybrid governance — enforces policies on terminal commands (exec_command) via hooks, provides advisory governance for other tools via implicit-activation skills, and records compliance-grade audit trails. Self-hosted via Docker — all data stays on your infrastructure.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "AxonFlow", "email": "hello@getaxonflow.com", diff --git a/scripts/pre-tool-check.sh b/scripts/pre-tool-check.sh index 6a78e2a..eb87e3f 100755 --- a/scripts/pre-tool-check.sh +++ b/scripts/pre-tool-check.sh @@ -79,8 +79,12 @@ if [ -z "$STATEMENT" ] || [ "$STATEMENT" = "null" ] || [ "$STATEMENT" = "{}" ]; exit 0 fi -# Call AxonFlow check_policy via MCP server -RESPONSE=$(curl -s --max-time "$REQUEST_TIMEOUT_SECONDS" -X POST "${ENDPOINT}/api/v1/mcp-server" \ +# Call AxonFlow check_policy via MCP server. +# +# Issue #1545 Direction 3: fail OPEN on any network-level failure (timeout, +# DNS failure, connection refused, 5xx). Only auth/config errors reported +# by AxonFlow fail closed (see the JSONRPC_ERROR handling below). +RESPONSE=$(curl -sS --max-time "$REQUEST_TIMEOUT_SECONDS" -X POST "${ENDPOINT}/api/v1/mcp-server" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ "${AUTH_HEADER[@]}" \ @@ -99,25 +103,49 @@ RESPONSE=$(curl -s --max-time "$REQUEST_TIMEOUT_SECONDS" -X POST "${ENDPOINT}/ap operation: "execute" } } - }')" 2>/dev/null || echo "") + }')" 2>/dev/null) +CURL_EXIT=$? + +# Any curl-level failure (exit != 0) means the network call failed — +# timeout, DNS failure, connection refused, TCP reset. Fail open. +if [ "$CURL_EXIT" -ne 0 ]; then + exit 0 +fi -# If AxonFlow is unreachable (empty response = network failure), fail-open +# Empty body from an otherwise-successful curl should also fail open +# (ambiguous: could be 204 No Content, could be a weird proxy). if [ -z "$RESPONSE" ]; then exit 0 fi -# Check for JSON-RPC error responses (auth failure, server error, etc.) -# Fail CLOSED on auth/config errors to prevent silent governance bypass. +# Check for JSON-RPC error responses and apply the fail-open / fail-closed +# policy from issue #1545 Direction 3: +# +# Fail CLOSED only on auth/config errors — where the operator can actually +# fix the problem — so a broken governance setup can never be silently +# bypassed. Network errors, server-internal errors, parse errors, and +# timeouts all fail OPEN to avoid blocking legitimate dev workflows on +# transient infrastructure issues. +# +# Auth errors (-32001): BLOCK — operator must fix AXONFLOW_AUTH +# Method not found (-32601): BLOCK — plugin version mismatch with agent +# Invalid params (-32602): BLOCK — plugin bug, operator should upgrade +# Parse errors (-32700): ALLOW — transient +# Internal errors (-32603): ALLOW — server-side fault, not operator's +# Everything else: ALLOW — unknown failure, default to allow JSONRPC_ERROR=$(echo "$RESPONSE" | jq -r '.error.message // empty' 2>/dev/null || echo "") if [ -n "$JSONRPC_ERROR" ]; then JSONRPC_CODE=$(echo "$RESPONSE" | jq -r '.error.code // 0' 2>/dev/null || echo "0") - # Auth errors (-32001) and internal errors (-32603) = block - # Parse errors (-32700) = allow (could be transient) - if [ "$JSONRPC_CODE" != "-32700" ]; then - echo "AxonFlow governance error: ${JSONRPC_ERROR}. Fix AxonFlow configuration to restore tool access." >&2 - exit 2 - fi - exit 0 + case "$JSONRPC_CODE" in + -32001|-32601|-32602) + echo "AxonFlow governance blocked: ${JSONRPC_ERROR} (code ${JSONRPC_CODE}). Fix AxonFlow configuration to restore tool access." >&2 + exit 2 + ;; + *) + # Transient or server-side — fail open. + exit 0 + ;; + esac fi # Parse the MCP response to get the tool result diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..e666d13 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# AxonFlow Codex plugin uninstall helper. +# +# Codex CLI's built-in `/plugins` uninstall only removes the registration +# from ~/.codex/config.toml and leaves the local-source plugin directory +# on disk. This helper cleans up the leftover: +# +# - ~/.codex/plugins/cache/axonflow-local/ (cache from local source) +# - ~/.codex/plugins/installed/axonflow-codex-plugin/ (if installed via marketplace) +# +# It does NOT remove ~/.codex/config.toml entries — run `/plugins` uninstall +# inside Codex CLI first. This script is the second half of the cleanup. +# +# Usage: +# ./scripts/uninstall.sh +# ./scripts/uninstall.sh --dry-run + +set -euo pipefail + +DRY_RUN=0 +if [ "${1:-}" = "--dry-run" ]; then + DRY_RUN=1 +fi + +remove_if_exists() { + local path="$1" + if [ -e "$path" ]; then + if [ "$DRY_RUN" = "1" ]; then + echo "[DRY RUN] Would remove: $path" + else + rm -rf "$path" + echo "Removed: $path" + fi + fi +} + +echo "AxonFlow Codex plugin cleanup" +echo "=============================" +echo + +remove_if_exists "$HOME/.codex/plugins/cache/axonflow-local" +remove_if_exists "$HOME/.codex/plugins/cache/axonflow-codex-plugin" +remove_if_exists "$HOME/.codex/plugins/installed/axonflow-codex-plugin" + +# Also strip AxonFlow entries from hooks.json if present. +HOOKS_FILE="$HOME/.codex/hooks.json" +if [ -f "$HOOKS_FILE" ] && grep -q "axonflow" "$HOOKS_FILE" 2>/dev/null; then + if [ "$DRY_RUN" = "1" ]; then + echo "[DRY RUN] Would prune AxonFlow entries from $HOOKS_FILE" + else + echo "Found AxonFlow entries in $HOOKS_FILE — edit manually to remove" + echo " (diff preview: grep -i axonflow $HOOKS_FILE)" + fi +fi + +# Also strip AxonFlow MCP server from config.toml if present. +CONFIG_FILE="$HOME/.codex/config.toml" +if [ -f "$CONFIG_FILE" ] && grep -qi "axonflow" "$CONFIG_FILE" 2>/dev/null; then + if [ "$DRY_RUN" = "1" ]; then + echo "[DRY RUN] Would report AxonFlow references in $CONFIG_FILE" + else + echo "Found AxonFlow references in $CONFIG_FILE — run '/plugins uninstall' in Codex CLI first" + echo " (diff preview: grep -i axonflow $CONFIG_FILE)" + fi +fi + +echo +echo "Done. Restart Codex CLI to complete the uninstall." From 13f2c872ced7a35045f6d7840b8862268185f857 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Wed, 8 Apr 2026 22:46:34 +0200 Subject: [PATCH 2/3] docs(changelog): promote Unreleased to v0.2.0 --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c04ae..705e0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -## [Unreleased] +## [0.2.0] - 2026-04-09 + +### Changed + +- **Hook fail-open/fail-closed hardening (issue #1545 Direction 3).** `scripts/pre-tool-check.sh` now distinguishes curl exit code (network failure) from HTTP success with an error body. Fail-closed (exit 2, block tool) only on operator-fixable JSON-RPC errors: auth failures (-32001), method-not-found (-32601), and invalid-params (-32602). Fail-open (exit 0, allow) on everything else: curl timeouts/DNS failures/connection refused, empty response, server-internal errors (-32603), parse errors (-32700), and unknown error codes. Prevents transient governance infrastructure issues from blocking legitimate dev workflows while still catching broken configurations. + +### Added + +- **`scripts/uninstall.sh` cleanup helper.** Codex CLI's built-in `/plugins` uninstall only removes the registration from `~/.codex/config.toml` and leaves the local-source plugin cache directory on disk. The new helper cleans up `~/.codex/plugins/cache/axonflow-local/`, `~/.codex/plugins/cache/axonflow-codex-plugin/`, and `~/.codex/plugins/installed/axonflow-codex-plugin/`. Supports `--dry-run`. Surfaces but does not modify `~/.codex/config.toml` or `~/.codex/hooks.json` (user-owned configuration). ### Security From f8e78b5a5a4f07cdb6ff8c9368d42d704e70610b Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Wed, 8 Apr 2026 22:59:09 +0200 Subject: [PATCH 3/3] test: update regression expectation to match new error message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-tool-check hook's auth-error stderr message changed from 'AxonFlow governance error:' to 'AxonFlow governance blocked:' as part of the #1545 Direction 3 fail-open hardening (where only operator-fixable errors block — the new message makes that distinction clearer). --- tests/test-hooks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-hooks.sh b/tests/test-hooks.sh index f7d5113..c558212 100755 --- a/tests/test-hooks.sh +++ b/tests/test-hooks.sh @@ -179,7 +179,7 @@ else STDERR_OUT=$(cat "$STDERR_FILE") rm -f "$STDERR_FILE" assert_eq "Exit code is 2 (block)" "2" "$EXIT_CODE" - assert_contains "Has governance error on stderr" "$STDERR_OUT" "governance error" + assert_contains "Has governance blocked on stderr" "$STDERR_OUT" "governance blocked" fi echo ""