Skip to content
Chapter 80Lesson 4

Quiz - Error discipline

Quiz progress

0 / 0

A reviewer flags this authorization helper as fail-open, even though it returns false (a refusal) when the membership query throws:

const isAdmin = async (): Promise<boolean> => {
try {
const { role } = await requireOrgUser();
return roleAtLeast(role, 'admin');
} catch {
return false;
}
};

At its own call site this denies correctly. So what’s the real problem the reviewer is pointing at?

false now collapses “proven not-admin” and “the check broke” into one value — the next caller who reads isAdmin() as a plain yes/no inherits a sentinel that lies, and the swallowed error means the operator never learns the gate is broken. Let the check throw and let one wrapper refuse.

Returning false is the fail-open bug — a broken check should return true so the request continues while the outage is investigated, and the catch should re-throw.

The helper is fine; the only fix needed is to log the error inside the catch before returning false, which restores the operator’s visibility.

You’ve internalized “refuse when in doubt.” Which of these reads on a Redis miss are genuinely a fail-closed decision? Select all that apply.

A feature-flag read that defaults to off — the flag gates a feature, so off is the safe side of an access decision.

A signature-verify step that returns false on both a bad signature and an HMAC library exception.

A theme-preference read that defaults to 'system'.

The two-message rule says the operator record should be rich — cause chain, ctx, the parsed input. A teammate logs input: redact(input.data) uniformly in the wrapper’s catch and calls it done. For which action is that still a leak?

A sign-in or password-change action — the parsed input object still holds the password, so for those actions you log the action name and userId and nothing of the input.

A cross-tenant read — the parsed input would carry another organization’s orgId, which the operator is never allowed to see.

None — once the input is the Zod-parsed object and run through redact, it is safe to log for every action.

A request to /invoices/[id] hits an invoice that exists but belongs to another tenant. The route wrapper’s status table returns 404, not 403. Why is 404 the more secure answer?

A 403 admits the resource exists and the caller simply isn’t allowed it — that confirms the ID is valid, which is itself a leak. A 404 is indistinguishable from “doesn’t exist,” so the attacker learns nothing; the operator log still records a cross_tenant_attempt with the truth.

403 is wrong because the caller is authenticated — 404 is the correct status for any authenticated-but-unauthorized request regardless of tenancy.

404 lets the framework’s not-found.tsx render instead of the error boundary, which is the only reason to prefer it; security-wise the two are equivalent.

Quiz complete

Score by topic