Fail-closed design
The server is fail-closed internally. This page is about keeping your code fail-closed when you build
on it. Most authorization vulnerabilities are introduced at the edge, not in the engine.
The one rule
Treat a denied decision and a thrown call identically: the answer is “no”.
try {
$decision = $client->check('warehouse:stock.adjust', $resource, $context);
} catch (\Throwable $e) {
// transport failure, timeout, malformed response — DENY
return $this->deny();
}
if (! $decision->allowed) {
return $this->deny();
}
// CATASTROPHIC — a network blip becomes a privilege escalation
try { $ok = $client->check(...); } catch (\Throwable) { $ok = true; }
The catch branch must deny. This single anti-pattern is the most common authorization CVE shape.
Default to deny in your own gates
If you add a gate the PDP doesn’t cover yet, default-deny:
$allowed = false; // start denied
if ($someCondition) { $allowed = true; }
return $allowed; // unknown paths stay denied
Never structure it as “allow unless we found a reason to deny” — a missed branch then leaks.
Don’t cache an allow past its assumptions
A cached “allowed” is only valid while its inputs hold. Step-up assurance expires, grants are revoked, and
sessions are killed:
- Re-check sensitive actions rather than trusting a cached allow.
- Key any cache on subject + permission + resource + policyVersion + AAL, and keep TTLs short.
laravel-iam-clientcaches decisions for you with sane invalidation — prefer it over rolling your own.
Gate sensitive actions on a fresh decision
For high-impact operations (money movement, deletion, privilege grants), call the PDP at the action, not
at page load, and honor requiresStepUp:
if ($decision->requiresStepUp) {
return $this->challengeStepUp(); // AAL2, then retry
}
Prefer 404 to 403 for tenant-scoped resources
When you expose data, return 404 for anything outside the caller’s tenant — never 403, which confirms
existence. See Multi-tenancy.
Test the denial path
The package’s own suite asserts denial for malformed input, missing data, exceptions and cross-tenant
access. Mirror that in your integration tests:
- a thrown PDP call denies;
- a missing grant denies;
- a failing ABAC condition denies;
- a cross-tenant resource returns 404.
A real outage in fail-closed mode looks like a spike in denials, not a silent leak. Alert on denial-rate
anomalies (the server’s metrics and anomaly signals help) so you notice an outage as an outage.
Next
- Deny-overrides & fail-closed — the theory.
- Securing the Admin API — fail-closed at the HTTP edge.
- Ask the PDP — the decision contract you gate on.