Learn how to keep serialization in the data-only zone in Clojure, avoid dangerous deserialization patterns, and validate payloads before they become trusted domain data.
Serialization: Converting data into a transport or storage format that can later be parsed back into a usable value.
The security question is not whether serialization exists. Every non-trivial system serializes data. The real question is what the parser is allowed to reconstruct and how much trust the system gives that payload after parsing.
The safest default in Clojure is to stay in the data-only zone:
clojure.edn/read-stringThe more the format or library tries to rebuild rich executable object graphs, the more dangerous deserialization becomes.
The visual below compares the safe mental model with the risky one: parse into plain data, validate it, and only then let the application treat it as trusted domain input.
Data-only formats are safer because they deserialize into ordinary values rather than arbitrary objects with surprising behavior. In Clojure, that usually means choosing formats that land as maps, vectors, strings, numbers, keywords, and similar plain data.
That is why clojure.edn/read-string is a safer tool than the full reader for untrusted data, and why general Java object deserialization deserves much more suspicion than plain JSON or Transit parsing.
A payload that parsed successfully is still untrusted. After parsing, the next step should be validation:
1(ns myapp.events
2 (:require [cheshire.core :as json]
3 [clojure.spec.alpha :as s]))
4
5(s/def ::event/type keyword?)
6(s/def ::event/id string?)
7(s/def ::event/body map?)
8(s/def ::event
9 (s/keys :req-un [::event/type ::event/id ::event/body]))
10
11(defn parse-event! [body]
12 (let [payload (json/parse-string body true)]
13 (when-not (s/valid? ::event payload)
14 (throw (ex-info "Invalid event payload" {:payload payload})))
15 payload))
This keeps the trust transition explicit:
read-string Is Not the Same as clojure.edn/read-stringThis distinction matters in Clojure security work:
clojure.core/read-string uses the full reader and is not the right default for untrusted inputclojure.edn/read-string is the safer data-oriented parser for EDNIf a page, message consumer, or admin tool reads untrusted text, this choice alone can determine whether the system stays in the data-only model or steps into a far riskier parser path.
Safe formats can still be abused through:
That means serialization safety is also about operational limits:
Some systems need assurance that serialized data was not altered in transit. Signatures or message authentication codes help with that, but they do not remove the need for validation. A payload can be authentic and still be:
Integrity and validation are related, but they are different jobs.
Parsing only means the bytes fit a format.
That is where surprising behavior and gadget-style exploitation often start.
Even data-only formats can become denial-of-service vectors if size and complexity are unconstrained.
Malformed or malicious payloads can still carry secrets, personal data, or exploit strings you do not want sprayed across logs.
Prefer data-only formats, use clojure.edn/read-string instead of the full reader for untrusted EDN, and treat parsing as only the first step before validation. Add payload-size limits, integrity protections where needed, and careful failure logging. In Clojure, safe serialization is mostly about refusing to blur the line between “plain data” and “objects with behavior.”