Secure Defaults and Defensive Programming in Clojure

Learn how to make Clojure systems safer by default through deny-by-default routing, bounded resource use, explicit parsing rules, and failure behavior that narrows rather than widens risk.

Secure defaults and defensive programming are closely related but not identical:

  • secure defaults make the ordinary configuration safer before any custom tuning
  • defensive programming assumes callers, integrations, or environments will eventually misbehave and constrains the damage

In a Clojure codebase, both ideas work best when the system is explicit about:

  • what is accepted
  • what is rejected
  • what resources are bounded
  • what happens on failure

Deny by Default Is Usually the Right Starting Point

Safer defaults often look restrictive:

  • routes unavailable until explicitly exposed
  • admin behavior off until intentionally enabled
  • unknown input keys rejected or ignored intentionally
  • TLS verification on
  • background tasks rate-limited or bounded
1(def default-flags
2  {:enable-admin-tools? false
3   :allow-unsafe-dev-shortcuts? false
4   :max-batch-size 500
5   :max-upload-bytes (* 10 1024 1024)})

This does not guarantee security, but it prevents “forgot to harden the default” from becoming the first incident.

Fail Closed Where the Risk Justifies It

When critical context is missing, do not silently continue with a broad fallback. Examples:

  • no identity present for a protected route
  • config missing a secret
  • malformed authorization token
  • unknown tenant or environment selector

The defensive posture is to stop or reject, not to guess.

Bound Resources Defensively

Security is not only confidentiality and authorization. It is also resilience against exhaustion:

  • payload size
  • nesting depth
  • queue length
  • retry fan-out
  • concurrency level
  • parser complexity

That means defensive programming should include operational ceilings, not just validation logic.

Parse Narrowly and Preserve Type Expectations

One recurring defensive pattern in Clojure is to keep parsing and normalization explicit instead of letting values stay loosely typed for too long:

1(defn parse-page-size [raw]
2  (let [n (Integer/parseInt raw)]
3    (if (<= 1 n 200)
4      n
5      (throw (ex-info "Invalid page size" {:page-size raw})))))

The goal is not endless small parser functions for style points. The goal is to avoid a system where stringly typed data flows too far before anyone constrains it.

Defensive Design Should Not Turn into Silent Corruption

There is a bad form of defensiveness that hides errors:

  • silently truncating data
  • substituting an unsafe fallback
  • swallowing an exception and pretending success
  • downgrading a security failure into “best effort”

A defensive system should narrow risk, not make failure harder to detect.

Where Defensive Programming Helps Most

High-value areas include:

  • parsers and format boundaries
  • authorization gates
  • queue consumers
  • file and archive handling
  • network retries and circuit breakers
  • interop with Java libraries or OS processes

These are the places where one unchecked assumption can become a chain reaction.

Common Failure Modes

Secure Defaults That Exist Only in Documentation

If the code path still boots with unsafe options unless someone remembers to change them, the default is not really secure.

Catch-All Error Handling That Hides Real Failures

Suppressing the error is not the same thing as containing the risk.

Unlimited Retries or Work Amplification

Many production incidents are defensive-programming failures in disguise because no one bounded the retry or resource loop.

Convenience Fallbacks That Broaden Authority

For example, a missing tenant becoming “use the default tenant” or a missing identity becoming “treat as internal.”

Practical Heuristics

Make the safe path the ordinary path, reject ambiguous privileged cases, and put clear resource ceilings around code that handles untrusted input or external systems. Then keep failures visible enough that operators can investigate them instead of silently widening risk. In Clojure, defensive programming is strongest when it narrows assumptions early and keeps the failure mode explicit.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026