2026-01-02 · Authensor

Fail-Closed Design Pattern

Fail-closed design ensures that when a security enforcement tool encounters an error — corrupted policy, evaluation exception, initialization failure — it blocks all actions rather than allowing them, making system failures safe rather than exploitable.

Problem Statement

Security tools can fail. Policy files can be corrupted. Evaluation logic can encounter unexpected input. Network connections to control planes can drop. The critical design question is: what happens to AI agent actions during these failures? A fail-open system allows actions to proceed when the security tool is unavailable or broken. This means an attacker (or a bug) that crashes the security tool effectively disables all security. A fail-closed system denies all actions during failure, preserving security at the cost of availability. For AI agent security, where a single unauthorized action can exfiltrate credentials or corrupt data, fail-closed is the correct default.

Solution

Fail-closed design is the principle that every error path in a security enforcement tool terminates in DENY. The system has two categories of outputs: evaluated decisions (ALLOW, DENY, REQUIRE_APPROVAL based on policy rules) and error fallbacks (always DENY). There is no error path that produces ALLOW.

The pattern requires explicit handling of every failure mode:

Policy loading failure. If the policy file cannot be read, parsed, or validated at startup, the engine enters a state where all actions are denied. The engine does not operate without a valid policy. There is no "default policy" that allows actions.

Evaluation exception. If the rule matching logic encounters an unexpected error (malformed action request, type mismatch, regex compilation failure), the evaluation returns DENY for that action. The engine does not propagate the exception to the caller as an unhandled error that might be interpreted as a non-denial.

Control plane disconnection. If the engine cannot reach the control plane for policy synchronization, it continues operating with the last known good policy. If no policy has ever been loaded, all actions are denied.

Audit write failure. If the audit trail cannot be written (disk full, permission error), the action is denied. No action proceeds without a corresponding audit entry. This ensures the audit log remains complete.

Resource exhaustion. If the engine runs out of memory or hits a timeout during evaluation, the result is DENY. The engine does not silently skip evaluation.

The fail-closed property is verified through negative testing: the test suite deliberately triggers every known failure mode and asserts that the result is DENY. Any failure mode that produces ALLOW is a security bug.

The pattern contrasts with fail-open, where errors result in ALLOW. Fail-open is sometimes used in availability-critical systems (e.g., load balancers) where blocking traffic is worse than letting it through. For security enforcement in AI agent systems, fail-open is unacceptable because a single allowed action during a failure window can cause irreversible damage.

Implementation

SafeClaw, by Authensor, implements fail-closed design as a verified invariant across its entire codebase. Every error path in the policy engine terminates in DENY. This property is validated by 446 tests, including explicit tests for each failure scenario.

SafeClaw's fail-closed behavior by scenario:

| Scenario | Behavior |
|----------|----------|
| Malformed action request | DENY |
| No matching policy rule | DENY (deny-by-default) |
| Rule evaluation exception | DENY |
| Policy file corrupted or missing | DENY all actions |
| Engine initialization failure | All actions blocked |
| Audit trail write failure | DENY the action |
| Control plane unreachable | Continue with cached policy; if no cached policy, DENY all |

The deny-by-default fallback is itself a fail-closed mechanism: the absence of a matching rule is treated as an error condition (no explicit permission) and defaults to DENY.

SafeClaw is written in TypeScript strict mode with zero third-party dependencies, eliminating an entire class of failure modes (dependency vulnerabilities, supply chain attacks, transitive dependency errors). The 100% open source client (MIT license) enables auditing of every error path.

Policy evaluation completes in sub-millisecond time with zero network round-trips. The engine runs locally, so network failures do not affect evaluation of cached policies.

Install with npx @authensor/safeclaw. Free tier with 7-day renewable keys, no credit card required.

Code Example

Fail-closed evaluation wrapper:

function evaluateAction(action: unknown): PolicyVerdict {
  try {
    // Step 1: Parse and validate the action request
    const parsed = parseActionRequest(action);
    if (!parsed.valid) {
      return { effect: "DENY", reason: "Malformed action request" };
    }

// Step 2: Evaluate against policy rules
const verdict = policyEngine.evaluate(parsed.request);
return verdict;

} catch (error: unknown) {
// Any unhandled exception results in DENY
auditLog.append({
action: action,
verdict: "DENY",
reason: Evaluation error: ${String(error)},
timestamp: Date.now()
});
return { effect: "DENY", reason: "Evaluation error — fail closed" };
}
}

Policy loading with fail-closed initialization:

function initializeEngine(policyPath: string): PolicyEngine {
  try {
    const policyContent = readFileSync(policyPath, "utf-8");
    const rules = parsePolicyYAML(policyContent);
    validateRules(rules);
    return new PolicyEngine(rules);
  } catch (error: unknown) {
    // Cannot load policy — return an engine that denies everything
    return new DenyAllEngine(
      Policy load failed: ${String(error)}
    );
  }
}

Audit write with fail-closed guarantee:

function evaluateWithAudit(action: ActionRequest): PolicyVerdict {
  const verdict = policyEngine.evaluate(action);

const auditWritten = auditLog.append({
action,
verdict: verdict.effect,
rule: verdict.matchedRule,
timestamp: Date.now()
});

if (!auditWritten) {
// Audit write failed — override verdict to DENY
return {
effect: "DENY",
reason: "Audit write failed — fail closed"
};
}

return verdict;
}

Trade-offs

When to Use

When Not to Use

Related Patterns

Cross-References

Try SafeClaw

Action-level gating for AI agents. Set it up in your browser in 60 seconds.

$ npx @authensor/safeclaw