🐛 Validate font-id team ownership in create-font-variant

Prevent cross-team font injection by checking that when a font-id
already has variants, they belong to the same team. This closes a
BOLA gap where a user with team edit permissions could create a
font variant referencing a font-id from another team.

AI-assisted-by: mimo-v2.5-pro
This commit is contained in:
Andrey Antukh 2026-07-28 09:12:58 +00:00
parent 319a2185c9
commit 20a2c5fcbf
2 changed files with 54 additions and 1 deletions

View File

@ -95,6 +95,18 @@
(declare create-font-variant)
(defn- check-font-team-ownership!
"When font-id already has variants belonging to a different team,
raises :not-found to prevent cross-team font injection."
[conn team-id font-id]
(let [row (db/get* conn :team-font-variant
{:font-id font-id}
{::db/columns [:team-id]})]
(when (and row (not= (:team-id row) team-id))
(ex/raise :type :not-found
:code :object-not-found
:hint "font does not belong to this team"))))
(def ^:private schema:create-font-variant
[:and
[:map {:title "create-font-variant"}
@ -168,8 +180,9 @@
[:process-font/global]]
::webhooks/event? true
::sm/params schema:create-font-variant}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id uploads] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id font-id uploads] :as params}]
(teams/check-edition-permissions! pool profile-id team-id)
(check-font-team-ownership! pool team-id font-id)
(quotes/check! cfg {::quotes/id ::quotes/font-variants-per-team
::quotes/profile-id profile-id
::quotes/team-id team-id})

View File

@ -922,3 +922,43 @@
:name "Valid Font Name"}
out (th/command! params)]
(t/is (th/success? out))))))
(t/deftest create-font-variant-rejects-foreign-font-id
;; N2-07: A user with edit permissions on their own team must not be
;; able to create a font variant using a font-id that already belongs
;; to another team (BOLA / CWE-639).
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
(let [prof1 (th/create-profile* 1 {:is-active true})
prof2 (th/create-profile* 2 {:is-active true})
team1 (:default-team-id prof1)
team2 (:default-team-id prof2)
font-id (uuid/custom 10 999)
data (-> (io/resource "backend_tests/test_files/font-1.ttf")
(io/read*))]
;; prof1 creates a font variant in team1 with font-id
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof1)
:team-id team1
:font-id font-id
:font-family "SharedFont"
:font-weight 400
:font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (nil? (:error out))))
;; prof2 tries to create a variant using the same font-id but
;; in team2 — must be rejected because font-id belongs to team1
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof2)
:team-id team2
:font-id font-id
:font-family "SharedFont"
:font-weight 700
:font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (some? (:error out)))
(t/is (= :not-found (-> out :error ex-data :type)))
(t/is (= :object-not-found (-> out :error ex-data :code)))))))