diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index f8fb6f0..584ce68 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "axonflow", "displayName": "AxonFlow Governance", "description": "Policy enforcement, PII detection, and audit trails for Cursor. Automatically evaluates governed tool inputs against 80+ governance policies, scans outputs for sensitive data, and records every decision in a compliance-grade audit trail. Self-hosted via Docker — all data stays on your infrastructure.", - "version": "0.2.0", + "version": "0.3.0", "author": { "name": "AxonFlow", "email": "hello@getaxonflow.com", diff --git a/CHANGELOG.md b/CHANGELOG.md index 118da5e..8fb6aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## [Unreleased] +## [0.3.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. ### Security diff --git a/scripts/pre-tool-check.sh b/scripts/pre-tool-check.sh index b51a469..50704c3 100755 --- a/scripts/pre-tool-check.sh +++ b/scripts/pre-tool-check.sh @@ -85,8 +85,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[@]}" \ @@ -105,25 +109,37 @@ RESPONSE=$(curl -s --max-time "$REQUEST_TIMEOUT_SECONDS" -X POST "${ENDPOINT}/ap operation: "execute" } } - }')" 2>/dev/null || echo "") + }')" 2>/dev/null) +CURL_EXIT=$? -# If AxonFlow is unreachable (empty response = network failure), fail-open -if [ -z "$RESPONSE" ]; then +# Any curl-level failure — timeout, DNS failure, connection refused, TCP +# reset — fails open. The only reason to fail closed is a well-formed +# auth/config error from AxonFlow itself, handled below. +if [ "$CURL_EXIT" -ne 0 ] || [ -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: +# +# 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 + ;; + *) + exit 0 + ;; + esac fi # Parse the MCP response to get the tool result diff --git a/tests/test-hooks.sh b/tests/test-hooks.sh index 22611e9..fab513a 100755 --- a/tests/test-hooks.sh +++ b/tests/test-hooks.sh @@ -172,7 +172,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 ""