Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
44 changes: 30 additions & 14 deletions scripts/pre-tool-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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[@]}" \
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test-hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
Loading