Reagent uses Clojure data structures to represent the React elements that will be created and mounted on the page.
In most cases, React does its best to avoid code injection attacks by making it
difficult to accidentally create elements that execute JavaScript code. For
example, <script>
element children’s JavaScript code text usually isn't executed
(TODO: Does this change in React 19?), and <img>
onLoad
and other similar callbacks
must be provided as functions, not strings of JavaScript code.
When using React with JSX, JSX can differentiate between React elements previously created with JSX and other JavaScript objects. In JavaScript applications, it shouldn't be possible to represent React elements in API responses.
<h1>{response.title}</h1>
Because Reagent deals directly with Clojure data structures, it is perfectly valid to build a value somewhere that contains Hiccup, which Reagent will then turn into React elements. Unfortunately, this also means it is possible to construct these Hiccup values IF your API or data source allows representing keywords—such as EDN and Transit data.
;; Regular use
(let [h [:h1 "Hello"]]
[:div h [:p "world"]])
;; Data from an API
(def title (r/atom nil))
;; Some code that retrieves an EDN/Transit value from an external API or localStorage
;; and stores the decoded value into the atom:
(reset! title [:div {:dangerouslySetInnerHTML {:__html "<img src=\"err\" onError=\"alert('danger')\"/>"}}])
[:div [:h1 @title]]
;; One safe approach
[:div [:h1 (str @title)]]
This does not mean users can write these data structures in form inputs and have those values processed as Hiccup. When you receive a value from a text field, it is stored as a string in a database and returned as such to the frontend.
However, if another security vulnerability exists—such as a man-in-the- middle (MITM) attack that modifies the data your API responds with—your Reagent frontend could process certain values as Hiccup. This could allow external data sources to return values that get executed in the user's browser.
localStorage
or similar
sourcesAll these scenarios require that the data is parsed using EDN, Transit, or other formats that allow the representation of Hiccup forms (i.e., vectors and keywords).
If such vulnerabilities exist, they may indicate broader security concerns beyond just Reagent’s interpretation of data structures as Hiccup.
Many other ClojureScript React libraries aren't vulnerable to this because they either process the Hiccup data at macro compilation time (Sablono, Hicada) or do not use Hiccup (UIx, Helix).
:dangerouslySetInnerHTML
values must be created using reagent.core/unsafe-html
function which tags them using a type, that can't be used accidentally from Transit and EDN
:style
can still be potentially dangerous, as there are
many attributes that can be used to control what the page looks like.str
calls.Reagent can't distinguish between [:div [:p]]
and (let [x [:p]] [:div x])
because the Hiccup interpretation happens at runtime. In both cases,
Reagent sees exactly the same data structure.
Libraries that handle Hiccup at macro compilation time would only see
the x
symbol in the latter case and could handle it differently.
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 |