From 746aaf3a0b189fbe00325713bdaef6ed95cc4f3a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 29 Jul 2026 09:37:59 +0000 Subject: [PATCH] :bug: Add bounding box dimension limit to prevent export DoS Add max-export-dimension constant (100000 units) and validate in calculate-dimensions. Reject exports when bounding box width, height, or position exceeds the limit to prevent resource exhaustion in the Chromium export pool. AI-assisted-by: mimo-v2.5-pro --- frontend/src/app/main/render.cljs | 20 +++-- .../render_dimensions_test.cljs | 78 +++++++++++++++++++ frontend/test/frontend_tests/runner.cljs | 2 + 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 frontend/test/frontend_tests/render_dimensions_test.cljs diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 0655545429..31108fa9f0 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -15,6 +15,7 @@ ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.exceptions :as ex] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] @@ -59,6 +60,7 @@ [rumext.v2 :as mf])) (def ^:const viewbox-decimal-precision 3) +(def ^:const max-export-dimension 100000) (def ^:private default-color clr/canvas) (mf/defc background @@ -82,12 +84,20 @@ (let [bounds (->> root-objects (map (partial gsb/get-object-bounds objects)) - (grc/join-rects))] + (grc/join-rects)) + bounds (-> bounds + (update :x mth/finite 0) + (update :y mth/finite 0) + (update :width mth/finite 100000) + (update :height mth/finite 100000))] + (when (or (> (:width bounds) max-export-dimension) + (> (:height bounds) max-export-dimension) + (> (+ (:x bounds) (:width bounds)) max-export-dimension) + (> (+ (:y bounds) (:height bounds)) max-export-dimension)) + (ex/raise :type :validation + :code :export-area-too-large + :hint "export area exceeds maximum allowed dimensions")) (-> bounds - (update :x mth/finite 0) - (update :y mth/finite 0) - (update :width mth/finite 100000) - (update :height mth/finite 100000) (grc/update-rect :position) (grc/fix-aspect-ratio aspect-ratio)))))) diff --git a/frontend/test/frontend_tests/render_dimensions_test.cljs b/frontend/test/frontend_tests/render_dimensions_test.cljs new file mode 100644 index 0000000000..d3773f6b36 --- /dev/null +++ b/frontend/test/frontend_tests/render_dimensions_test.cljs @@ -0,0 +1,78 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en EspaƱa SL + +(ns frontend-tests.render-dimensions-test + (:require + [app.common.geom.rect :as grc] + [app.common.geom.shapes.bounds :as gsb] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.ids-map :as cthi] + [app.common.test-helpers.shapes :as cths] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [app.main.render :as render] + [cljs.test :as t :include-macros true])) + +(defn- make-objects + "Create a proper objects map with a root frame and the given shapes." + [& shapes] + (let [root-frame (cts/setup-shape {:id uuid/zero + :type :frame + :parent-id uuid/zero + :frame-id uuid/zero + :name "Root Frame" + :shapes (mapv :id shapes)}) + objects {uuid/zero root-frame}] + (reduce (fn [objs shape] + (assoc objs (:id shape) (assoc shape :frame-id uuid/zero))) + objects + shapes))) + +(t/deftest calculate-dimensions-normal-bounds + (t/testing "Normal bounding box should pass" + (let [shape1 (cts/setup-shape {:type :rect :x 100 :y 100 :width 200 :height 150}) + shape2 (cts/setup-shape {:type :rect :x 400 :y 300 :width 100 :height 100}) + objects (make-objects shape1 shape2) + result (render/calculate-dimensions objects nil)] + (t/is (some? result)) + (t/is (<= (:width result) render/max-export-dimension)) + (t/is (<= (:height result) render/max-export-dimension))))) + +(t/deftest calculate-dimensions-extreme-width + (t/testing "Extreme width should throw export-area-too-large" + (let [shape (cts/setup-shape {:type :rect :x 0 :y 0 :width 200000 :height 100}) + objects (make-objects shape)] + (t/is (thrown-with-msg? + js/Error + #"export area exceeds maximum allowed dimensions" + (render/calculate-dimensions objects nil)))))) + +(t/deftest calculate-dimensions-extreme-height + (t/testing "Extreme height should throw export-area-too-large" + (let [shape (cts/setup-shape {:type :rect :x 0 :y 0 :width 100 :height 200000}) + objects (make-objects shape)] + (t/is (thrown-with-msg? + js/Error + #"export area exceeds maximum allowed dimensions" + (render/calculate-dimensions objects nil)))))) + +(t/deftest calculate-dimensions-extreme-position + (t/testing "Shape at extreme position should throw export-area-too-large" + (let [shape (cts/setup-shape {:type :rect :x 500000 :y 500000 :width 100 :height 100}) + objects (make-objects shape)] + (t/is (thrown-with-msg? + js/Error + #"export area exceeds maximum allowed dimensions" + (render/calculate-dimensions objects nil)))))) + +(t/deftest calculate-dimensions-exactly-at-limit + (t/testing "Bounding box exactly at limit should pass" + (let [shape (cts/setup-shape {:type :rect :x 0 :y 0 :width render/max-export-dimension :height render/max-export-dimension}) + objects (make-objects shape) + result (render/calculate-dimensions objects nil)] + (t/is (some? result)) + (t/is (<= (:width result) render/max-export-dimension)) + (t/is (<= (:height result) render/max-export-dimension))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 21c7d23c06..e0c06b4a08 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -51,6 +51,7 @@ [frontend-tests.plugins.tokens-test] [frontend-tests.plugins.utils-test] [frontend-tests.plugins.value-objects-test] + [frontend-tests.render-dimensions-test] [frontend-tests.render-wasm.process-objects-test] [frontend-tests.render-wasm.text-editor-caret-color-test] [frontend-tests.svg-fills-test] @@ -160,6 +161,7 @@ 'frontend-tests.ui.gradient-handlers-test 'frontend-tests.ui.layout-container-multiple-test 'frontend-tests.ui.measures-menu-props-test + 'frontend-tests.render-dimensions-test 'frontend-tests.text-editor-paste-guard-test 'frontend-tests.ui.settings-password-schema-test 'frontend-tests.ui.settings-shortcuts-test