Web › Module 10 › Lesson 1
Fail-Open vs Fail-Closed
A10: when checks error, do you deny or allow? Design the failure mode.
Visual · fail_open_closed
Gate stuck open vs stuck shut when the sensor dies. — router STOP — SAFE Choose fail-closed for authz on apps YOU own.
Opening
Exceptional conditions are when your security check throws — A10 asks what happens next.
OWASP Top 10:2025 A10 Mishandling of Exceptional Conditions covers unsafe fallbacks: authz service timeout → allow; certificate verify error → continue; feature flag store down → disable all checks. Attacker goal: induce errors that open doors. Cyberlium: name fail-open vs fail-closed for controls on systems YOU own — not crash foreign prod “to watch the fallback.” Practical commands: ; if it is a home router login, STOP and use SAFE http://The teaching toy shows broken fail-open vs fixed fail-closed. No hydra. No nmap. No DoS against strangers. Next: error messages that leak. Today: failure modes.
1. Fail-closed: deny when unsure for security decisions
For authentication and authorization, prefer fail-closed: if the policy engine errors, deny and log. Users see a safe unavailable message; attackers do not gain access from your outage. Document the choice per control on YOUR app. Prove it with curls to /broken/authz vs /fixed/authz on the 8775 toy.
Availability still matters — design timeouts, retries, and degraded read-only modes that do not skip authz.
Command guide
A10 Exceptions — Fail-closed
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install curl
macOS: Built-in
Windows: Built-in (PowerShell: Invoke-WebRequest)
═══ COMMANDS ═══
Command — copy this
SAFE="http://127.0.0.1:8775"
Command — copy this
curl -sS -m 3 -D - "$SAFE/fixed/authz?boom=1" | head -n 18
2. Fail-open: when it is intentional and bounded
Some non-security features fail-open for UX (recommendations missing → empty list). Mixing that habit into authz is A10. If you must fail-open a secondary control, bound it: short TTL, alert, never for money movement or admin.
Write one intentional fail-open and one forbidden fail-open for your scope.
Command guide
A10 Exceptions — Fail-open
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install curl
macOS: Built-in
Windows: Built-in (PowerShell: Invoke-WebRequest)
═══ COMMANDS ═══
Command — copy this
SAFE="http://127.0.0.1:8775"
Command — copy this
curl -sS -m 3 -I "$SAFE/" | head -n 12 curl -sS -m 3 -D - "$SAFE/broken/authz?boom=1" | head -n 16
3. Timeouts, defaults, and catch-all handlers
catch (Exception) { return true; } on a permission check is a classic fail-open. Default-allow flags in config are the same. Review YOUR code for broad catches around security decisions. Fix to deny + log + metric.
Circuit breakers should not short-circuit into allow for authz.
Command guide
A10 Exceptions — Timeouts, defaults, and catch-all handlers
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install curl
macOS: Built-in
Windows: Built-in (PowerShell: Invoke-WebRequest)
═══ COMMANDS ═══
Command — copy this
SAFE="http://127.0.0.1:8775"
Command — copy this
curl -sS -m 3 -D - "$SAFE/broken/authz?boom=1" | head -n 18
4. Induced errors are an attacker technique — defend, do not practice on strangers
Attackers may flood dependencies to force fallbacks. Your defense is fail-closed design and capacity — not DoS labs against systems you do not own, and not hydra on the router. Cite A10. Original Cyberlium — not official OWASP certification.
chmod 600 notes.
5. Wrong vs right: allow-on-error vs deny-on-error for authz
Same word “fallback,” opposite safety.
Wrong
catch-all returns allow. TLS verify failures ignored. Crash foreign prod to “test fallback.” Hydra 192.168.0.1. nmap the LAN. Bind 0.0.0.0.
Right
Identify DEMO; router → SAFE 127.0.0.1:8775. Authz fail-closed; bounded non-security fail-open documented; lock fail-open-notes.txt. Next: Error Messages That Leak.
6. Hands-on: fail-closed toy on 8775 + fail-open-notes.txt
Start a10_fail_closed_toy.py (binds 127.0.0.1:8775). GET /broken/authz?boom=1 allows. GET /fixed/authz?boom=1 denies. Record. No foreign outage labs.
Command guide
fail_open_notes.sh — DEMO identify + fail-closed toy 8775
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install curl sudo apt install nmap sudo apt install python3
macOS:
Command — copy this
brew install nmap brew install python3
Windows: Built-in (PowerShell: Invoke-WebRequest)
Command — copy this
choco install nmap # or download https://nmap.org/download.html
Download https://python.org/downloads/
═══ COMMANDS ═══
Command — copy this
cd "$HOME/cyberlium-lab"
Command — copy this
cat > a10_fail_closed_toy.py << 'PY'
"""Broken fail-open vs fixed fail-closed. Bind loopback only."""
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
HOST = "127.0.0.1" # bind only — never 0.0.0.0, never argv
PORT = 8775
if HOST != "127.0.0.1":
raise SystemExit("refusing non-loopback bind")
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
print("[demo]", fmt % args)
def _send(self, code: int, body: str):
data = body.encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def _boom(self, qs):
return (qs.get("boom") or ["0"])[0] == "1"
def do_GET(self):
u = urlparse(self.path)
qs = parse_qs(u.query)
if u.path == "/broken/authz":
if self._boom(qs):
return self._send(200, "ALLOW (fail-open on error) — insecure")
return self._send(200, "allow owner")
if u.path == "/fixed/authz":
if self._boom(qs):
return self._send(403, "DENY (fail-closed on error)")
return self._send(200, "allow owner")
return self._send(
200,
"Cyberlium A10 fail-closed toy. DEMO writeup: http://192.168.0.1/ "
"GET /broken/authz?boom=1 GET /fixed/authz?boom=1",
)
print("bind", HOST, PORT)
print("demo_writeup_url http://192.168.0.1/")
print("if 192.168.0.1 is a router, curl", f"http://{HOST}:{PORT}/")
HTTPServer((HOST, PORT), Handler).serve_forever()
PYCommand — copy this
cat > a10_l01_practical_commands.sh << 'SH' echo; cat /tmp/a10_open.body; echo echo; cat /tmp/a10_closed.body; echo SH
Command — copy this
{Mission: fail-open-notes.txt (mode 600)
1) 0.0.1:8775 after starting a10_fail_closed_toy.py.2) Run broken vs fixed authz curls. Document fail-closed for authz and any bounded fail-open for non-security features. 3) chmod 600 $HOME/cyberlium-lab/fail-open-notes.txt. No foreign outage labs. No hydra. No nmap.
Stuck? Ask Cyberlium AI Mentor
If “resilience” still means “allow when broken,” ask for a hint. Try: "Hint only: why authz should fail-closed; when fail-open is OK; why 192.168.0.1 router login is OUT OF SCOPE; where notes live?"
A10 starts with choosing deny when security checks cannot complete — proven on DEMO/SAFE 8775. Original Cyberlium — not official OWASP certification. Next — Error Messages That Leak.
Knowledge Check
APPLY: Policy service timeout currently returns allow. A10 issue?
Multiple choice
Knowledge Check
APPLY: True or False: catch (Exception) { return true; } around permission checks is safe.
True or False
Knowledge Check
APPLY: curl of http://192.168.0.1/ is router admin. Crash it to watch fail-open?
Multiple choice