dead-programs-tell-no-lies-why-crashing-early-is-your-best-security-defense.md

Dead Programs Tell No Lies: Why Crashing Early is Your Best Security Defense

by Saint
pragmatic-programmerdefensive-codingsoftware-entropyfault-tolerancedevsecops

Dead Programs Tell No Lies: Why Crashing Early Is Your Best Security Defense

In both software engineering and security, we're constantly fighting software entropy — the inexorable increase of disorder and rot in a system. Developers are usually trained to optimize for the "happy path" of clean inputs and expected behavior, but the real world is messy, unpredictable, and hostile.

So when the "impossible" happens, how should your program react? The pragmatic and secure answer is simple: crash early.


The Fallacy of "It Can't Happen"

Many programmers fall into a dangerous cognitive trap: assuming certain edge cases are so unlikely that they don't need to be handled. We tell ourselves:

  • "This value will never be null."
  • "This file will always close successfully."
  • "The selector for this switch statement can only be 1, 2, or 3."

Code written with this mindset leaves silent vulnerabilities waiting to explode. If a logic error occurred several million instructions ago, a variable might hold an unexpected type, or a switch statement might fall through to an unhandled default. This is exactly why every switch statement needs a default clause — you need a way to know when the impossible has, in fact, occurred.

Coding defensively means continuously validating that your data and your running production environment actually match your core assumptions.


Catch and Release Is for Fish

When an anomaly or exception does occur, many developers write verbose, deeply nested exception-handling blocks that catch an error, log a generic message, and then re-raise it:

try do
  add_score_to_board(score)
rescue
  InvalidScore -> Logger.error("Can't add invalid score. Exiting"); raise
  BoardServerDown -> Logger.error("Can't add score: board is down. Exiting"); raise
end

The Pragmatic Programmers call this pattern "catch and release," and they advise against it. It bloats your source code, and worse, it tightly couples your code to the specific exceptions the underlying methods happen to throw today. If those methods change what they raise, your handling code goes silently out of date.

A better approach: let exceptions propagate naturally up the stack to a central, well-tested supervisor or orchestrator that knows how to handle and log them systematically — rather than scattering ad hoc handling logic throughout the codebase.


Why a Dead Program Is Safer Than a Crippled One

If your code discovers that something supposed to be impossible has happened, the program is no longer in a trustworthy state. Continuing execution under these conditions is dangerous. A compromised or corrupted process running on autopilot might:

  • Write corrupted data to a production database.
  • Command a connected physical device — an IoT appliance or a medical device — into a destructive loop.
  • Expose sensitive internal memory or stack traces to an attacker actively probing for an entry vector.

As the authors of The Pragmatic Programmer put it: "A dead program normally does a lot less damage than a crippled one."

By crashing early, you contain the blast radius of the bug and generate a clean stack trace right at the point of failure. That immediately points administrators and developers to the root cause, instead of letting the failure propagate silently and surface later as a hard-to-diagnose side effect somewhere downstream.


Managing the Crash: The Supervisor Pattern

Crashing a process doesn't mean your entire platform goes down. Fault-tolerant languages like Erlang and Elixir formalize this with a "let it crash" philosophy.

Instead of writing defensive clutter into every function, systems built this way rely on supervisors and supervisor trees. When an individual worker process crashes due to an impossible state, its supervisor detects the failure, cleans up any associated resources, and restarts the process in a known-good state — preserving system resilience without letting corrupted state poison the rest of the running application.


A Paranoia Checklist for Pragmatic Developers

  • Read the error message. Stop tabbing away from exception screens — the platform is telling you exactly what went wrong.
  • Write assertions for the "impossible." If a variable should never be null, assert it — and leave assertions active in production, where they act as a critical safety net against real-world anomalies.
  • Minimize your attack surface. If a process or module doesn't need elevated privileges or broad access, restrict it.
  • Never trust external input. Sanitize and validate every piece of data crossing your trust boundary before running logic on it.

© 2026 ShellnotesWithSaint