Add diagnostic keys to SSRF validation exceptions

Add :uri and :scheme/:host keys to exceptions raised by
`validate-uri` for better error diagnostics. Also fix a bug
where (str url) was used instead of (str uri) in the
host-missing exception path.

Update the existing blocked-target test to verify the new :uri
key, and add three new tests covering scheme rejection, missing
host, and DNS failure error paths. All 27 tests pass with 60
assertions and 0 failures.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrey Antukh 2026-05-18 15:57:55 +00:00
parent 5c423c3678
commit 1b6b367951
2 changed files with 46 additions and 6 deletions

View File

@ -184,13 +184,17 @@
(not (contains? allowed-schemes (str/lower scheme))))
(ex/raise :type :validation
:code :ssrf-blocked-target
:hint "url scheme is not allowed"))
:hint "url scheme is not allowed"
:uri (str uri)
:scheme scheme))
;; Validate host presence
(when (or (nil? host) (str/blank? host))
(ex/raise :type :validation
:code :ssrf-blocked-target
:hint "url host is missing"))
:hint "url host is missing"
:uri (str uri)
:host host))
;; Check allowlist
(let [allowed-hosts (cf/get :ssrf-allowed-hosts #{})
@ -210,13 +214,15 @@
(when (or (nil? addresses) (zero? (alength addresses)))
(ex/raise :type :validation
:code :ssrf-blocked-target
:hint "url host could not be resolved"))
:hint "uri host could not be resolved"
:uri (str uri)))
;; All-or-nothing: if ANY resolved address is blocked, reject
(when (some blocked-address? (seq addresses))
(ex/raise :type :validation
:code :ssrf-blocked-target
:hint "url target is not allowed")))))
:hint "uri target is not allowed"
:uri (str uri))))))
(str uri)))
(defn safe-url?

View File

@ -130,8 +130,42 @@
(ssrf/validate-uri "http://127.0.0.1/foo")
(t/is false "should have thrown")
(catch Exception e
(t/is (= :validation (:type (ex-data e))))
(t/is (= :ssrf-blocked-target (:code (ex-data e)))))))
(let [data (ex-data e)]
(t/is (= :validation (:type data)))
(t/is (= :ssrf-blocked-target (:code data)))
(t/is (= "http://127.0.0.1/foo" (:uri data)))))))
(t/deftest validate-url-throw-on-scheme
(try
(ssrf/validate-uri "file:///etc/passwd")
(t/is false "should have thrown")
(catch Exception e
(let [data (ex-data e)]
(t/is (= :validation (:type data)))
(t/is (= :ssrf-blocked-target (:code data)))
(t/is (= "file:///etc/passwd" (:uri data)))
(t/is (= "file" (:scheme data)))))))
(t/deftest validate-url-throw-on-missing-host
(try
(ssrf/validate-uri "http:///path")
(t/is false "should have thrown")
(catch Exception e
(let [data (ex-data e)]
(t/is (= :validation (:type data)))
(t/is (= :ssrf-blocked-target (:code data)))
(t/is (= "http:///path" (:uri data)))
(t/is (nil? (:host data)))))))
(t/deftest validate-url-throw-on-dns-failure
(try
(ssrf/validate-uri "http://nonexistent.invalid/foo")
(t/is false "should have thrown")
(catch Exception e
(let [data (ex-data e)]
(t/is (= :validation (:type data)))
(t/is (= :ssrf-blocked-target (:code data)))
(t/is (= "http://nonexistent.invalid/foo" (:uri data)))))))
;; ---------------------------------------------------------------------------
;; http/req automatic SSRF validation