diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index ffd227e12e..633697985d 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -189,7 +189,8 @@ (::ws/routes cfg) - ["/api" {:middleware [[mw/cors]]} + ["/api" {:middleware [[mw/cors] + [sec/client-header-check]]} (::oidc/routes cfg) (::rpc.doc/routes cfg) (::rpc/routes cfg)]]])) diff --git a/backend/src/app/http/security.clj b/backend/src/app/http/security.clj index 1834123c0a..a1c7262c79 100644 --- a/backend/src/app/http/security.clj +++ b/backend/src/app/http/security.clj @@ -37,3 +37,19 @@ :compile (fn [_ _] (when (contains? cf/flags :sec-fetch-metadata-middleware) wrap-sec-fetch-metadata))}) + +(defn- wrap-client-header-check + "Check for a penpot custom header to be present as additional CSRF + protection" + [handler] + (fn [request] + (let [client (yreq/get-header request "x-client")] + (if (some? client) + (handler request) + {::yres/status 403})))) + +(def client-header-check + {:name ::client-header-check + :compile (fn [_ _] + (when (contains? cf/flags :client-header-check-middleware) + wrap-client-header-check))}) diff --git a/backend/test/backend_tests/http_middleware_security.clj b/backend/test/backend_tests/http_middleware_security.clj index d051e71d84..3804f2186e 100644 --- a/backend/test/backend_tests/http_middleware_security.clj +++ b/backend/test/backend_tests/http_middleware_security.clj @@ -44,4 +44,16 @@ (t/is (= 200 (::yres/status resp5))) (t/is (= 403 (::yres/status resp6))))) +(t/deftest client-header-check + (let [request1 (mock-request :get "some") + request2 (mock-request :post nil) + + handler (fn [request] + {::yres/status 200}) + handler (#'sec/wrap-client-header-check handler) + resp1 (handler request1) + resp2 (handler request2)] + + (t/is (= 200 (::yres/status resp1))) + (t/is (= 403 (::yres/status resp2))))) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 08babe3bc1..a497b29de5 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -139,7 +139,11 @@ ;; Security layer middleware that filters request by fetch ;; metadata headers - :sec-fetch-metadata-middleware}) + :sec-fetch-metadata-middleware + + ;; Security layer middleware that check the precense of x-client + ;; http headers and enables an addtional csrf protection + :client-header-check-middleware}) (def all-flags (set/union email login varia)) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index aa7635c212..1ab7c11092 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -51,7 +51,8 @@ (defn default-headers [] - {"x-frontend-version" (:full cfg/version)}) + {"x-frontend-version" (:full cfg/version) + "x-client" (str "penpot-frontend/" (:full cfg/version))}) (defn fetch [{:keys [method uri query headers body mode omit-default-headers credentials]