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
- Gain: System failures never result in unauthorized action execution.
- Gain: Attackers cannot disable security by crashing the enforcement tool.
- Gain: Audit trail completeness is guaranteed — no unlogged actions can proceed.
- Gain: Deterministic failure behavior simplifies incident response: if the tool is broken, all actions are blocked.
- Cost: System failures block all agent functionality, including legitimate actions. Availability is sacrificed for security.
- Cost: Operators must monitor the health of the security tool. An undetected failure in a fail-closed system silently disables the agent.
- Cost: Policy loading errors during deployment can block the agent entirely until the configuration is fixed.
When to Use
- Every security enforcement tool for AI agents. Fail-closed is not optional for security tooling — it is the correct default for any component whose purpose is to prevent unauthorized actions.
- When the cost of a single unauthorized action (credential exfiltration, data corruption, production outage) exceeds the cost of temporary agent downtime.
- In compliance-regulated environments where audit trail completeness is mandatory.
- When agents have access to sensitive resources (credentials, PII, infrastructure).
When Not to Use
- Non-security components where availability takes priority over security. A logging library or metrics collector may appropriately fail open to avoid disrupting the primary application.
- Monitoring and alerting layers (Layer 5 in defense in depth) where the tool observes but does not enforce. Monitoring tools that fail open still provide value when they recover.
- Cases where a secondary fail-closed enforcement layer already exists. If the container sandbox (Layer 4) will block the action regardless, an inner layer might fail open without creating a security gap. However, relying on this assumes the secondary layer is functioning correctly.
Related Patterns
- Deny-by-Default — Deny-by-default is a specific case of fail-closed design applied to the "no matching rule" scenario.
- Defense in Depth — Multiple fail-closed layers ensure that any single layer's failure is caught by another.
- Immutable Audit Log — The fail-closed guarantee extends to audit writes: no action proceeds without logging.
- Sidecar Gating — The sidecar must implement fail-closed behavior in all error paths.
- Simulation Before Enforcement — Simulation mode tests fail-closed behavior without blocking production traffic.
Cross-References
- Test Coverage Reference — 446 tests validating fail-closed behavior across all error scenarios.
- Policy Engine Architecture Reference — Fail-closed behavior in the evaluation pipeline.
- Security Model Reference — Threat model and failure mode analysis.
- SafeClaw vs. Prompt Guardrails Comparison — Prompt guardrails fail open (agents ignore them); SafeClaw fails closed.
- AI Agent Security Risks FAQ — Risks mitigated by fail-closed design.
Try SafeClaw
Action-level gating for AI agents. Set it up in your browser in 60 seconds.
$ npx @authensor/safeclaw