Secure by Default: Open-Sourcing Our Security Primitives

engineering
security
startup
defense-in-depth
Author

Mike McCourt

Published

March 4, 2026

Making the secure path the path of least resistance for our engineering team.

Sturdy Statistics processes sensitive enterprise data every day. That makes “secure by default” an operational requirement for us.

Security should not depend on a developer remembering to check a box or manually sanitize input. It should be embedded into the libraries that define how systems are built.

To that end, we are open-sourcing two libraries that now form part of our production security architecture:

Both originated from concrete production needs. They are opinionated, conservative tools designed to make the secure path the path of least resistance for our engineering team.

1. malli-firewall: Perimeter Defense for Ring Endpoints

Web applications must treat all incoming data as untrusted. In many Ring applications, validation is spread across middleware, route handlers, and ad-hoc checks, which increases the chance that a missing validation becomes a vulnerability. We built malli-firewall for this purpose. It is a high-performance validation library designed specifically for Ring endpoints.

How it works

Malli-firewall provides a macro, with-schema, which acts as a strict gatekeeper for your route handlers.

(def LoginRequest
  [:map {:closed true}
   [:username  NonBlankString]
   [:token     NonBlankString]

   [:__anti-forgery-token {:optional true} string?]])

(defn handle-login [request]
  (with-schema LoginRequest request
    ;; The body ONLY executes if:
    ;; 1. The data matches the schema
    ;; 2. The data is safe
    (let [{:keys [username token]} (:params request)]
      (auth/login! username token))))

The design guarantees that your business logic never runs against invalid data. If the schema check fails, the firewall returns a 400 response immediately, without running the body.

Why we chose Malli

Under the hood, this library is built on Malli. If you aren’t familiar with Malli, it is a data-driven schema library for Clojure. We chose it because it treats schemas as pure data, offers excellent runtime performance, and provides humanized error reporting. Malli allows us to define the “shape” of our expected data simply and clearly, and malli-firewall makes it easy to enforce.

Solving the “Keyword Leak” (DoS Protection)

One vulnerability in Clojure web applications is the Keyword Leak. Because Clojure interns keywords indefinitely, an attacker sending random strings as JSON keys can exhaust the JVM’s Metaspace, causing a Denial of Service (DoS) attack. In long-lived JVM services, unbounded keyword interning can crash an otherwise stable production process.

While standard middleware like wrap-keyword-params blindly interns user input, malli-firewall uses smart keywordization: it only converts strings to keywords if they actually exist in your Malli schema. Arbitrary attacker input is stripped and never interned. On validation failure, it also intelligently retains “near-miss” keys as strings, so we can send helpful error messages (e. g., Did you mean ‘token’?) without interning them or otherwise compromising safety.

By wrapping endpoints in with-schema, our developers can focus on the logic of the endpoint rather than validation; the system automatically refuses to run on invalid data.

2. bailey: Operational Safety for Server-Side Keys

While malli-firewall protects our perimeter, bailey protects our data at rest. It is a small, opinionated library designed to manage the lifecycle of server-side encryption keys.

Why we rely on Tempel

Bailey does not implement any cryptography. We rely on Tempel, a Clojure data security framework which wraps the JVM’s native crypto facilities. We love Tempel because it provides a high-level, misuse-resistant encryption API. Tempel makes it easy to handle data securely; bailey makes it automatic within our systems.

Separation of Mechanism and Policy

Bailey provides a utility to generate an offline backup keypair. The private key should be stored in a vault; the public key can be embedded in resources so that it is included inside the app at build time. (We manage our public keys as private maven dependencies, so they are versioned and explicitly tracked.)

The hosting application provides bailey with a callback to retrieve a server secret as a byte[]. This callback might wrap a KMS call or unlock a TPM-sealed secret. Crucially, the function must return a fresh byte array on each call; Bailey zeros the array immediately after use to clear it from memory.

When the server starts up, bailey ensures that the server has a valid Tempel keychain, generating one if necessary. This keychain is encrypted using both the server secret and the backup key, ensuring an ultimate “break-glass” recovery path even if the server secret is completely lost. We designed Bailey so that even a severe operational failure (such as a lost host, TPM hardware failure, or loss of the KMS key) does not imply irreversible data loss.

Bailey provides simple 1-argument functions, bailey/encrypt and bailey/decrypt, to handle data using the server keychain. This makes it easy to ensure everything sensitive is encrypted before logging, writing to a DB, or serializing.

Transparent Rotation

Bailey also offers transparent rotation via bailey/rotate-keys!. Rotation appends a new key to the keychain; encryption always uses the latest key, while decryption tries each key in sequence. Rotation is therefore a low-cost operation that never risks data loss. We use the chime library (in our case, via agentti) to schedule quarterly key rotations automatically:

(def server-key-rotate-schedule
  (->> (chime/periodic-seq
        ;; ANCHOR: January 1st of the CURRENT year at Midnight
        (-> (ZonedDateTime/now (ZoneId/of "America/Los_Angeles"))
            (.withMonth 1)
            (.withDayOfMonth 1)
            (.withHour 0)
            (.withMinute 0) ;; 12:00 AM
            (.withSecond 0)
            (.withNano 0))

        ;; STEP: 3 Months
        (Period/ofMonths 3))

       (chime/without-past-times)))

In practice, this means no engineer needs to remember to rotate keys; the system rotates them deterministically and safely on a fixed cadence.

By relying on Bailey, our engineers know that encryption is deterministic, auditable, and inherently recoverable through deliberate, offline administrative action. With initialization and rotation all automated, our engineers only need to call (bailey/encrypt <plaintext-secret>) or (bailey/decrypt <ciphertext>) to secure data.

Trust Through Code

Security is too important to be left to good intentions. By abstracting our threat models into reusable, automated libraries, we allow our engineers to focus on solving complex statistical problems, knowing the application architecture is watching their backs.

We are sharing malli-firewall and bailey so that you can review the code, audit our practices, and – if your threat model aligns with our approach – use them to harden your own Clojure applications. Check out the repositories on Clojars and GitHub:

malli-firewall github clojars
bailey github clojars