A fail-closed workflow defaults to blocking or holding an action whenever a required check can't complete successfully, rather than letting the action proceed as if the check had passed. This is the safer default for most security-relevant AI workflow decisions, and achieving it takes explicit design attention, since many common programming patterns default to the opposite behavior, letting execution continue past an unhandled error unless something specifically guards against it.
Identify every dependency whose failure needs an explicit decision
Start by listing every dependency a given workflow step relies on to make a security-relevant decision: an authorization service, a policy evaluation call, an approval workflow, a rate limit check. For each one, explicitly decide and document what should happen if that dependency goes down, times out, or returns an unexpected error, rather than leaving this behavior to whatever the underlying code happens to do by default when an exception occurs.
Default new dependencies to fail-closed unless explicitly justified otherwise
When adding a new dependency to a workflow, default its failure behavior to fail-closed, and require an explicit justification and sign-off for any dependency that should instead fail-open, proceeding despite an unavailable check. This flips the common default, where a dependency's failure behavior is whatever happens to result from unhandled error propagation, usually letting execution continue, and instead makes the safer behavior the path of least resistance for a developer adding new functionality.
Distinguish availability risk from security risk explicitly
Fail-closed design trades availability for security: a workflow that blocks sensitive actions during a dependency outage takes some availability hit during that outage, in exchange for not exposing the riskier alternative of proceeding without the check that outage prevented. Make this tradeoff explicit and deliberate for each specific dependency, weighing how often that dependency is expected to go down and how severe the consequence of proceeding without its check would actually be, rather than treating the fail-closed default as automatically correct for every dependency regardless of these specific factors.
Test fail-closed behavior by actually simulating the failure
The only reliable way to confirm a workflow actually fails closed is to simulate the specific failure condition directly, following the testing guidance covered elsewhere, disconnecting or returning an error from the dependency in a test environment and confirming the workflow's actual observed behavior matches the intended design. A fail-closed intention that's never been tested against a real simulated failure is an assumption, not a verified property of the system.
Design timeouts carefully, since an unbounded wait is its own failure mode
A dependency call with no timeout, or an excessively long one, can leave a workflow hanging indefinitely rather than failing closed promptly, which creates its own availability problem distinct from the security question. Set explicit, reasonably short timeouts for security-relevant dependency calls, and make sure a timeout triggers the same fail-closed handling as an explicit error response, rather than getting treated as a separate, unhandled case.
Communicate fail-closed events clearly for operational response
When a workflow blocks an action due to a fail-closed dependency failure, make sure this event gets logged and, where appropriate, alerted distinctly from a routine authorization denial, since a spike in fail-closed blocking due to a dependency outage needs a different operational response, restoring the dependency, than a spike in routine denials, which might point to a change in user behavior or an attack pattern instead.
Avoid retry logic that silently converts a fail-closed design into fail-open
Retry logic meant to handle transient failures gracefully can inadvertently undermine fail-closed design if it eventually gives up and proceeds with the action after exhausting retries, rather than maintaining the block. Make sure retry logic explicitly maintains the fail-closed outcome if all retries get exhausted, rather than defaulting to proceed just because the retry loop itself finished running.
Periodically audit actual dependency failure behavior in production
Beyond initial testing, periodically review production incident history and logs specifically for any dependency failure, and confirm the workflow's actual behavior during that real event matched the intended fail-closed design, since a real failure event gives direct evidence beyond simulated testing that the design holds under genuine, unplanned conditions.
Where TELEON fits
TELEON's policy enforcement, applied through the gateway or supported middleware, is designed to fail closed when a policy decision cannot be reached, providing this behavior for the specific enforcement point it covers, which application-level fail-closed design for other dependencies in the broader workflow should complement.
Identifying every other security-relevant dependency in a given workflow, and explicitly designing and testing its specific fail-closed behavior, remains application-level engineering work that has to be done directly by the team building that workflow.
A short checklist
- List every security-relevant dependency and explicitly decide its
failure behavior.
- Default new dependencies to fail-closed unless explicitly justified
otherwise.
- Weigh the availability-versus-security tradeoff deliberately for each
specific dependency.
- Test fail-closed behavior by actually simulating the failure
condition.
- Set explicit, reasonably short timeouts, treated the same as explicit
errors.
- Log and alert fail-closed events distinctly from routine denials.
- Ensure retry logic maintains the fail-closed outcome if retries are
exhausted.
- Periodically audit real production failure events against the intended
design.
Fail-closed design requires explicit, deliberate attention at every point a workflow depends on a check that might fail, since the common default behavior of unhandled errors in most systems tends toward the less safe alternative unless specifically designed and tested against.
