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/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 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." 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 ""