A high-performance, security-focused Clojure validation library built on Malli. Designed for web applications that need to ingest untrusted Ring parameters safely, while also providing human-friendly error reporting.
malli-firewall provides a macro with-schema which wraps a Ring handler to provide schema coercion & validation.
It converts valid keys to keywords, so your handler code may use keyword idioms.
However, unlike the Ring middleware wrap-keyword-params, it will not intern unexpected keys or arbitrary user input.
Unexpected keys which are suspected typos of valid keys are left in the map as strings human-friendly error reporting.
By default, all other keys are stripped automatically so they are never visible to handler code.
Optionally, you may re-bind the dynamic var malli-firewall.web/*strip-unknown-keys* to false; in this case unexpected keys are retained as strings, so that closed schemas may fail validation.
The wrapped handler does not run unless schema validation passes, and will only run with a valid schema.
On validation failure, with-schema returns a 400 response.
This response is controlled by the dynamic var malli-firewall.web/*bad-request-handler*, which you may re-bind to your own 400 handler.
malli-firewall was originally developed to meet the internal security and operational requirements of Sturdy Statistics. It is published as open source to support transparency, auditability, and reuse, but its design is intentionally conservative and driven by real production needs. We may not accept feature requests that dilute its focus.
Add to deps.edn:
{:deps {com.sturdystats/malli-firewall {:mvn/version "VERSION"}}}
Define your requirements using standard Malli syntax.
(def LoginRequest
[:map {:closed true}
[:username NonBlankString]
[:token NonBlankString]
[:next {:optional true} RelativeURI]
[:error {:optional true} any?]
[:__anti-forgery-token string?]])
Use the with-schema macro in your Ring handlers to guard your logic.
(defn handle-login [request]
(with-schema schemas/LoginRequest request
(let [{:keys [username token]} (:params request)]
;; If code reaches here, request params are guaranteed:
;; 1. Valid: Schema validation passed.
;; 2. Keywordized: Valid keys are keywords (safe for internal use).
;; 3. Sanitized: Extra "garbage" keys are stripped by default.
;; 4. Typed: Values (e.g., "123" -> 123) are coerced per schema.
(auth/login! username token))))
If you have multiple maps, use the with-schemas macro instead:
(with-schemas {:params schemas/LoginRequest
:path-params schemas/UserPath}
request
(handler request))
If validation fails, the handler is not executed and a 400 Bad Request
response is returned.
Example error:
{:error "Bad Request"
:message "Invalid request parameters"
:details {:user-id ["missing required key"]}
:flat-message "user-id: missing required key"}
(require '[sturdy.malli-firewall.schemas :as s])
(require '[sturdy.malli-firewall.core :as f])
(let [params {:user-id "bob" :tokens "howdy"}
{:keys [error]} (f/validate s/LoginRequest params)]
error)
;; => {:message "Invalid request parameters",
;; :problems
;; {:username ["missing required key"],
;; :tokens ["should be spelled :token"]}}
For inline validation (similar to truss.have), have-schema ensures data is valid or throws an exception.
(let [params (f/have-schema schemas/LoginRequest params)]
...)
On failure it throws:
(ex-info "Bad request" {:message ...
:problems ...})
You can customize the firewall behavior using dynamic variables:
| Variable | Default | Description |
|---|---|---|
*bad-request-handler* | JSON 400 | A function (fn [request details]) called on failure. |
*strip-unknown-keys* | true | When false, unknown keys are kept as safe strings. |
Sturdy Schema uses a multi-stage transformation process to ensure data integrity:
This library avoids the "Keyword Leak" common in many Clojure web apps. By using the smart-key-transformer, the internal keyword function is only called on strings defined in your schemas. Randomly generated keys from attackers stay as strings and are discarded, protecting the JVM's Metaspace.
The library includes a dedicated test suite for memory safety. You can verify that no interning occurs for garbage keys by running:
clj -X:test
Apache License 2.0
Copyright © Sturdy Statistics
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |