Learn the high-value defenses for SQL injection, XSS, CSRF, and related web weaknesses in Clojure applications, with an emphasis on boundaries rather than checkbox security.
Common web vulnerabilities: Recurring weaknesses such as SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF) that appear when untrusted input crosses a boundary without the right controls.
Clojure gives you some structural advantages: immutable data, explicit data flow, and a culture of small pure functions. None of that removes the need to defend request boundaries, query boundaries, rendering boundaries, or browser-state boundaries. Most web security bugs still happen where untrusted data changes meaning.
SQL injection happens when attacker-controlled input is allowed to change the meaning of a database command. The primary defense is parameterization, not string filtering.
1(ns myapp.user-store
2 (:require [next.jdbc :as jdbc]))
3
4(defn find-user-by-email [ds email]
5 (jdbc/execute-one! ds
6 ["select user_id, email, status from users where email = ?" email]))
The important part is not the library name. It is the boundary rule:
Validation still matters for business rules, but OWASP is explicit that validation is not the primary SQL injection defense. Parameterized queries are.
XSS happens when untrusted content becomes executable browser code. The primary defense is context-aware output encoding and safe templating defaults.
In practical Clojure web code, that means:
The biggest mistake here is thinking “we validated the input” is enough. Validation checks whether data is acceptable. It does not automatically make that data safe in an HTML or JavaScript context.
CSRF matters when a browser automatically sends authenticated state such as cookies and the application accepts a forged state-changing request.
For classic server-rendered or cookie-backed Clojure apps, the usual controls are:
SameSite cookie settingsGET1(ns myapp.web
2 (:require [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]))
3
4(def app
5 (wrap-anti-forgery handler))
That middleware is only part of the story. You still need routes and forms that treat write actions as privileged, intentional operations.
Even when the main bug is elsewhere, browser-facing headers matter:
Content-Security-Policy helps contain XSSStrict-Transport-Security reinforces HTTPS useX-Content-Type-Options: nosniff reduces content-type confusionReferrer-Policy limits accidental data leakageThese do not replace application security, but they make common failures less exploitable.
Many production vulnerabilities are not in handwritten request handlers at all. They live in:
For Clojure services, that means reviewing not only application code but also:
Validation is important, but it does not replace parameterized queries, output encoding, or CSRF protections.
The browser is not the security boundary. The backend must still validate, authorize, and encode correctly.
Cookie-backed apps need explicit CSRF controls even when the handlers look small and harmless.
Frameworks help, but XSS, SQL injection, and browser-state bugs still appear when developers bypass the safe path.
Protect the boundary where data changes meaning. Use parameterized queries for SQL, context-aware output encoding for browser rendering, and anti-forgery protections for cookie-backed state changes. Then add security headers, dependency hygiene, and operational review so one missed control does not become a full compromise. In Clojure, security improves most when the code makes trust boundaries obvious instead of pretending user input is just another map.