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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.11-beta.0 — 2026-10-13

### Fixes

- A PostToolUse deny on Claude Code and OpenCode now tells the model the output was flagged, not that the call was blocked: the tool had already run and its output had already reached the model (#882)

## 1.0.10 — 2026-10-05

Action needed if you use `instruct` policies with Hermes: an `instruct` no longer stops the call it reminds about. The call runs and the reminder is placed first in its result, so a policy written to prevent an action (for example, a direct API write that should go through an approved script) must become a `deny` before you upgrade. Hermes 0.20.0 is supported again: on 1.0.9 the plugin loaded there with no hooks and every tool call ran unchecked. After upgrading, run `failproofai update` and restart each Hermes gateway profile by name. Collects 1.0.10-beta.0 to beta.1 below.
Expand Down
2 changes: 1 addition & 1 deletion __tests__/e2e/helpers/hook-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function assertPermissionRequestDeny(result: HookRunResult): void {
export function assertPostToolUseDeny(result: HookRunResult): void {
expect(result.exitCode).toBe(0);
const output = result.parsed?.hookSpecificOutput as Record<string, unknown> | undefined;
expect(output?.additionalContext).toMatch(/Blocked/i);
expect(output?.additionalContext).toMatch(/Flagged/i);
}

/**
Expand Down
14 changes: 7 additions & 7 deletions __tests__/fixtures/two-tier/unconfigured-golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"continue\\\",\\\"reason\\\":\\\"MANDATORY ACTION REQUIRED from failproofai (policies: failproofai/p-inst, custom/p-inst2): do x first\\\\nand y\\\\n\\\\nYou MUST complete the above action(s) NOW. Do NOT ask the user for confirmation \u2014 execute the required action(s), then attempt to finish your task again.\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-inst\",\"policyNames\":[\"failproofai/p-inst\",\"custom/p-inst2\"],\"reason\":\"do x first\\nand y\",\"decision\":\"instruct\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PreToolUse\\\",\\\"permissionDecision\\\":\\\"deny\\\",\\\"permissionDecisionReason\\\":\\\"Blocked Bash by failproofai because: not allowed, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PermissionRequest\\\",\\\"decision\\\":{\\\"behavior\\\":\\\"deny\\\",\\\"message\\\":\\\"Blocked Bash by failproofai because: not allowed, as per the policy configured by the user\\\"}}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: not allowed, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: not allowed, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"not allowed\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"MANDATORY ACTION REQUIRED from failproofai (policy: failproofai/p-deny): not allowed\\n\\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation \u2014 execute the required action, then attempt to finish your task again.\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"Blocked Bash by failproofai because: not allowed, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
Expand Down Expand Up @@ -107,7 +107,7 @@
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"deny\\\",\\\"reason\\\":\\\"Blocked session start by failproofai because: not allowed, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PreToolUse\\\",\\\"permissionDecision\\\":\\\"deny\\\",\\\"permissionDecisionReason\\\":\\\"Blocked Bash by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PermissionRequest\\\",\\\"decision\\\":{\\\"behavior\\\":\\\"deny\\\",\\\"message\\\":\\\"Blocked Bash by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"Blocked by policy: failproofai/p-deny\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"MANDATORY ACTION REQUIRED from failproofai (policy: failproofai/p-deny): Blocked by policy: failproofai/p-deny\\n\\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation \u2014 execute the required action, then attempt to finish your task again.\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"Blocked Bash by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
Expand Down Expand Up @@ -139,7 +139,7 @@
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"deny\\\",\\\"reason\\\":\\\"Blocked session start by failproofai because: Blocked by policy: failproofai/p-deny, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"Blocked by policy: failproofai/p-deny\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PreToolUse\\\",\\\"permissionDecision\\\":\\\"deny\\\",\\\"permissionDecisionReason\\\":\\\"Blocked Bash by failproofai because: first, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PermissionRequest\\\",\\\"decision\\\":{\\\"behavior\\\":\\\"deny\\\",\\\"message\\\":\\\"Blocked Bash by failproofai because: first, as per the policy configured by the user\\\"}}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: first, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: first, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"first\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"MANDATORY ACTION REQUIRED from failproofai (policy: failproofai/p-deny): first\\n\\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation \u2014 execute the required action, then attempt to finish your task again.\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"Blocked Bash by failproofai because: first, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"first\",\"decision\":\"deny\"}",
Expand Down Expand Up @@ -190,7 +190,7 @@
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"continue\\\",\\\"reason\\\":\\\"MANDATORY ACTION REQUIRED from failproofai (policy: custom/p-inst): after a crash\\\\n\\\\nYou MUST complete the above action(s) NOW. Do NOT ask the user for confirmation \u2014 execute the required action(s), then attempt to finish your task again.\\\"}\",\"stderr\":\"\",\"policyName\":\"custom/p-inst\",\"policyNames\":[\"custom/p-inst\"],\"reason\":\"after a crash\",\"decision\":\"instruct\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PreToolUse\\\",\\\"permissionDecision\\\":\\\"deny\\\",\\\"permissionDecisionReason\\\":\\\"Blocked Bash by failproofai because: not allowed. use the staging db, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PermissionRequest\\\",\\\"decision\\\":{\\\"behavior\\\":\\\"deny\\\",\\\"message\\\":\\\"Blocked Bash by failproofai because: not allowed. use the staging db, as per the policy configured by the user\\\"}}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: not allowed. use the staging db, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: not allowed. use the staging db, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"not allowed. use the staging db\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":2,\"stdout\":\"\",\"stderr\":\"MANDATORY ACTION REQUIRED from failproofai (policy: failproofai/p-deny): not allowed. use the staging db\\n\\nYou MUST complete the above action NOW. Do NOT ask the user for confirmation \u2014 execute the required action, then attempt to finish your task again.\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"Blocked Bash by failproofai because: not allowed. use the staging db, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"policyName\":\"failproofai/p-deny\",\"reason\":\"not allowed. use the staging db\",\"decision\":\"deny\"}",
Expand Down Expand Up @@ -276,9 +276,9 @@
"{\"exitCode\":0,\"stdout\":\"\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"allow\",\"policyName\":null,\"policyNames\":[],\"reason\":null,\"matchedPolicies\":\"4ee41d5ded79645ec5b432eb\",\"toolName\":\"mcp__github__delete_repo\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PermissionRequest\\\",\\\"decision\\\":{\\\"behavior\\\":\\\"deny\\\",\\\"message\\\":\\\"Blocked Bash by failproofai because: sudo commands are blocked, as per the policy configured by the user\\\"}}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/block-sudo\",\"policyNames\":[\"failproofai/block-sudo\"],\"reason\":\"sudo commands are blocked\",\"matchedPolicies\":\"a37448f70f44189f727efb05\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"allow\",\"policyName\":null,\"policyNames\":[],\"reason\":null,\"matchedPolicies\":\"a37448f70f44189f727efb05\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: JWT token detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-jwt\",\"policyNames\":[\"failproofai/sanitize-jwt\"],\"reason\":\"JWT token detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: Private key content detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-private-key-content\",\"policyNames\":[\"failproofai/sanitize-private-key-content\"],\"reason\":\"Private key content detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Blocked Bash by failproofai because: Database connection string with credentials detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-connection-strings\",\"policyNames\":[\"failproofai/sanitize-connection-strings\"],\"reason\":\"Database connection string with credentials detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: JWT token detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-jwt\",\"policyNames\":[\"failproofai/sanitize-jwt\"],\"reason\":\"JWT token detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: Private key content detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-private-key-content\",\"policyNames\":[\"failproofai/sanitize-private-key-content\"],\"reason\":\"Private key content detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUse\\\",\\\"additionalContext\\\":\\\"Flagged Bash output by failproofai because: Database connection string with credentials detected in tool output, as per the policy configured by the user\\\"}}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-connection-strings\",\"policyNames\":[\"failproofai/sanitize-connection-strings\"],\"reason\":\"Database connection string with credentials detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"allow\",\"policyName\":null,\"policyNames\":[],\"reason\":null,\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
"{\"exitCode\":0,\"stdout\":\"\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"allow\",\"policyName\":null,\"policyNames\":[],\"reason\":null,\"matchedPolicies\":\"4f53cda18c2baa0c0354bb5f\",\"toolName\":null}}",
"{\"exitCode\":0,\"stdout\":\"{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"Blocked Bash by failproofai because: JWT token detected in tool output, as per the policy configured by the user\\\"}\",\"stderr\":\"\",\"evaluation\":{\"decision\":\"deny\",\"policyName\":\"failproofai/sanitize-jwt\",\"policyNames\":[\"failproofai/sanitize-jwt\"],\"reason\":\"JWT token detected in tool output\",\"matchedPolicies\":\"f631901f4dafea29cc21aefa\",\"toolName\":\"Bash\"}}",
Expand Down
4 changes: 2 additions & 2 deletions __tests__/hooks/policy-evaluator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("hooks/policy-evaluator", () => {
const parsed = JSON.parse(result.stdout);
expect(parsed.hookSpecificOutput.hookEventName).toBe("PostToolUse");
expect(parsed.hookSpecificOutput.additionalContext).toBe(
"Blocked Read by failproofai because: JWT found, as per the policy configured by the user",
"Flagged Read output by failproofai because: JWT found, as per the policy configured by the user",
);
expect(result.policyName).toBe("failproofai/jwt-scrub");
expect(result.reason).toBe("JWT found");
Expand Down Expand Up @@ -120,7 +120,7 @@ describe("hooks/policy-evaluator", () => {
expect(parsed.decision).toBeUndefined();
expect(
(parsed.hookSpecificOutput as Record<string, unknown>).additionalContext,
).toBe("Blocked Read by failproofai because: JWT found, as per the policy configured by the user");
).toBe("Flagged Read output by failproofai because: JWT found, as per the policy configured by the user");
});

it("other event types use exit 2 for deny", async () => {
Expand Down
Loading