Absence Isn't Safety: Five Fail-Open Bugs in an AI Agent's Guardrails
Five fail-open bugs in an AI agent's policy engine show how missing data was misread as permission—and how each was fixed with default-deny logic.
An MCP-mediated Slack agent, claude-code-slack-channel, built its security story on a signed audit journal and a per-call policy engine. Independent review found the same underlying flaw recurring in five different places: whenever a check lacked the data it needed to decide, it defaulted to the permissive outcome instead of the safe one. A deny rule scoped to a path silently vanished when no structured arguments were available; a fully wiped audit log verified as 'clean' because zero broken links means zero problems; a prototype-inherited property could slip past a truthiness-based channel-policy gate; and an attacker who sheared off the head of an unsigned event chain and renumbered the rest produced a journal that verified perfectly, because it was internally consistent — just untrue.
The fixes share one principle: when a security check has nothing to check, it must fail closed, not open. The policy engine now escalates to a human reviewer rather than auto-approving when it can't see the input a scoped rule depends on. The audit verifier enforces a minimum event count so an empty log fails instead of passing. Channel-policy lookups route through a single own-property-checking chokepoint instead of seven inconsistent guards. Signed chains get an operator-pinned genesis hash that a renumbering attack can't recompute.
A notable twist: the hardening effort itself briefly introduced a new fail-open risk. Replacing a safe optional-chaining read with a stricter check that throws on missing data made a security gate's behavior depend on which exception handler happened to catch it — the exact kind of nondeterminism a gate must never have. The broader lesson for engineers building agentic systems: default-deny belongs at every layer, and missing data must be treated as an alarm, not a green light.