Safely produce javascript strings including user inputs in your clojure app. Mainly intended for use with Datastar.
In Datastar apps, we often need to create snippets of javascript. The existing options for that are:
- String concatenation/
format - datastar-expressions
When user inputs could plausibly be included in these javascript snippets (e.g. :data-init (str "alert('Hello from " username "')")), we run the risk of opening ourselves up to injection attacks (like in the example we just saw).
Datastar-expressions is a noble attempt at implementing these snippets with Clojure's syntax, but my measurements have shown that there's a lot of overhead from translating those snippets to strings.
This library requires us to write javascript in strings, which we all agree is repulsive, but to do so safely and with little overhead.
Accepts one or more forms, typically strings.
Provides 4 interpolation forms in strings:
- #{} to insert escaped forms
- #() to insert escaped invocations
- #!{} to insert unescaped forms
- #!() to insert unescaped invocations
Returns an escaped string.
Use like clojure.core/str if you don't like interpolation:
(def quality "bad")
(com.knowclick.safe-js/str "console.log(" quality ")")
;;=> "console.log(\"bad\")"Pass maps/vectors/etc if it pleases you:
(def user {:name "bart" :action "alert('xss')"})
(js/str user)
;; => "{\"name\":\"bart\",\"action\":\"alert('xss')\"}"
;; And if you want Bart to really get into trouble:
(def user {:name "bart" :action (js/! "alert('xss')")})
(js/str user)
;; => "{\"name\":\"bart\",\"action\":alert('xss')}"Some interpolation examples:
(def user {:name "bart"})
[:div {:data-text (js/str "`Hello, #{ (:name user) }`")}]
;; => [:div {:data-text "`Hello, \"bart\"`"}]
[:div {:data-text (js/str "`Hello, #(:name user)`")}]
;; => [:div {:data-text "`Hello, \"bart\"`"}]
(def $signal "$foo")
[:div {:data-init (js/str "#!{ $signal }=75")}]
;; => [:div {:data-init "$foo=75"}]
Given a string (presumably of javascript code), returns an object that will allow that string to pass through escaping functions and macros un-escaped.
Sanitize some data. Data wrapped with ! are not escaped.
Like str but wraps the result in !.
See the tests for more examples.