A logbook for curious waters
← Software Engineering Field Guide

Secure Defaults and Error Handling

Before you begin: Configuration, authorization decisions, exceptions, and responses.

Secure defaults avoid granting access without an explicit decision. Safe error handling reports failure without exposing sensitive internals or turning an uncertain authorization result into permission. A missing setting and a failed permission lookup should not silently make private data public. 1

Suppose downloads are private unless public access has been explicitly enabled. An absent configuration value should leave them private. Likewise, if the permission service cannot answer, the application should not proceed as though access was granted.

The example returns response objects, not real HTTP responses. An HTTP adapter would map the status and body into its framework’s response mechanism.

public sealed record Response(int Status, string Body);

public static class Downloads
{
    public static bool PublicAccess(bool? configured)
    {
        return configured == true;
    }

    public static Response Read(Func<bool> authorized, Func<string> load)
    {
        try
        {
            if (!authorized()) return new Response(403, "Access denied.");
            return new Response(200, load());
        }
        catch (System.IO.IOException)
        {
            return new Response(503, "Service temporarily unavailable.");
        }
    }
}
Console.WriteLine(Downloads.PublicAccess(null)); // False
Response response = Downloads.Read(() => false, () => "private contents");
Console.WriteLine(response.Status); // 403

The content loader is not called after denial. An I/O failure from either permission lookup or loading produces a failure response, not private content or the exception message.

from dataclasses import dataclass


@dataclass(frozen=True)
class Response:
    status: int
    body: str


def public_access(configured):
    return configured is True


def read_download(authorized, load):
    try:
        if not authorized():
            return Response(403, "Access denied.")
        return Response(200, load())
    except OSError:
        return Response(503, "Service temporarily unavailable.")
print(public_access(None))  # False
response = read_download(lambda: False, lambda: "private contents")
print(response.status)  # 403

The configuration check requires an actual Boolean True, not a truthy string such as "false". Parse configuration into validated types before using it; malformed settings should be reported at startup rather than interpreted opportunistically.

Fail closed, but preserve useful diagnosis

The code handles one documented family of I/O failures. Unanticipated exceptions still need a safe framework-level handler. A public response should not reveal stack traces, connection strings, local paths, or private records. Authorized operators need separate diagnostics with a correlation identifier and appropriately restricted access. 1

A generic client message should not mean silently discarding every error. Nor should a broad catch return an empty success result that hides lost work. Distinguish rejection, temporary unavailability, cancellation, and unexpected failure in the internal contract.

Failing closed means refusing protected work when permission cannot be established. It does not mean every application operation must stop whenever any unrelated logging service is down. Decide failure policy according to the operation’s risk and requirements, and test it explicitly.

Defaults belong at more than one layer

Keep development diagnostics off in production, restrict initial account permissions, and require deliberate exposure of administrative endpoints. Do not rely on users to discover and disable unsafe defaults after installation.

The example’s public_access helper is not wired into the separate authorization callback; it illustrates configuration policy independently. A real endpoint needs a coherent access rule, and tests must verify the deployed route uses it. Safe helpers that callers bypass provide no protection.

The most revealing tests are often failure tests: omitted settings, unavailable dependencies, invalid configuration, and denied identities. A successful request alone says little about what the program does when its assumptions stop holding.

Sources

  1. OWASP — Error Handling Cheat Sheet.
  2. OWASP — Authorization Cheat Sheet, including deny-by-default behavior.