mirror of
https://github.com/penpot/penpot.git
synced 2026-08-28 23:59:00 +00:00
🐛 Filter share-link tokens in get-view-only-bundle response
The get-view-only-bundle RPC command returned all share-link tokens for a file, allowing an anonymous holder of a restrictive share-link to enumerate and use more permissive tokens. When authenticating via a share-link, the response now only includes the share-link used for authentication, preventing token disclosure and scope escalation. Implemented using TDD: - RED: Test demonstrates vulnerability (all tokens visible) - GREEN: Filter share-links when (:type perms) = :share-link - Verified all existing tests still pass Closes #11285 AI-assisted-by: qwen3.7-plus
This commit is contained in:
parent
fda6d56139
commit
e57d949a7e
@ -56,7 +56,7 @@
|
||||
(assoc :can-read true)))
|
||||
|
||||
(defn- get-view-only-bundle
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id ::perms] :as params}]
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id share-id ::perms] :as params}]
|
||||
(let [file (bfc/get-file cfg file-id)
|
||||
|
||||
project (db/get conn :project
|
||||
@ -89,16 +89,18 @@
|
||||
(mapv (fn [{:keys [id] :as lib}]
|
||||
(merge lib (bfc/get-file cfg id)))))
|
||||
|
||||
links (->> (db/query conn :share-link {:file-id file-id})
|
||||
(mapv (fn [row]
|
||||
(-> row
|
||||
(update :pages db/decode-pgarray #{})
|
||||
;; NOTE: the flags are deprecated but are still present
|
||||
;; on the table on old rows. The flags are pgarray and
|
||||
;; for avoid decoding it (because they are no longer used
|
||||
;; on frontend) we just dissoc the column attribute from
|
||||
;; row.
|
||||
(dissoc :flags)))))
|
||||
links (cond->> (->> (db/query conn :share-link {:file-id file-id})
|
||||
(mapv (fn [row]
|
||||
(-> row
|
||||
(update :pages db/decode-pgarray #{})
|
||||
;; NOTE: the flags are deprecated but are still present
|
||||
;; on the table on old rows. The flags are pgarray and
|
||||
;; for avoid decoding it (because they are no longer used
|
||||
;; on frontend) we just dissoc the column attribute from
|
||||
;; row.
|
||||
(dissoc :flags)))))
|
||||
(= :share-link (:type perms))
|
||||
(filterv #(= (:id %) share-id)))
|
||||
|
||||
fonts (db/query conn :team-font-variant
|
||||
{:team-id (:id team)
|
||||
|
||||
@ -128,3 +128,64 @@
|
||||
(let [result (:result out)]
|
||||
(t/is (contains? result :file))
|
||||
(t/is (contains? result :project)))))))
|
||||
|
||||
(t/deftest share-link-token-disclosure
|
||||
(let [owner (th/create-profile* 1 {:is-active true})
|
||||
proj-id (:default-project-id owner)
|
||||
|
||||
file (th/create-file* 1 {:profile-id (:id owner)
|
||||
:project-id proj-id
|
||||
:is-shared false})
|
||||
|
||||
page-a (get-in file [:data :pages 0])
|
||||
page-b (uuid/random)
|
||||
|
||||
;; Add a second page to the file
|
||||
_ (th/command! {::th/type :update-file
|
||||
::rpc/profile-id (:id owner)
|
||||
:id (:id file)
|
||||
:session-id (uuid/random)
|
||||
:revn 0
|
||||
:vern 0
|
||||
:changes [{:type :add-page
|
||||
:id page-b
|
||||
:page {:id page-b
|
||||
:name "Page B"
|
||||
:options {}
|
||||
:objects {}}}]})
|
||||
|
||||
;; Create Link A: restrictive (no pages, team-only comments/inspect)
|
||||
link-a (th/command! {::th/type :create-share-link
|
||||
::rpc/profile-id (:id owner)
|
||||
:file-id (:id file)
|
||||
:pages #{}
|
||||
:who-comment "team"
|
||||
:who-inspect "team"})
|
||||
link-a-id (get-in link-a [:result :id])
|
||||
|
||||
;; Create Link B: permissive (all pages, all can comment/inspect)
|
||||
link-b (th/command! {::th/type :create-share-link
|
||||
::rpc/profile-id (:id owner)
|
||||
:file-id (:id file)
|
||||
:pages #{page-a page-b}
|
||||
:who-comment "all"
|
||||
:who-inspect "all"})
|
||||
link-b-id (get-in link-b [:result :id])]
|
||||
|
||||
(t/testing "restrictive share-link holder cannot see other share-link tokens"
|
||||
(let [out (th/command! {::th/type :get-view-only-bundle
|
||||
:share-id link-a-id
|
||||
:file-id (:id file)})
|
||||
err (:error out)
|
||||
result (:result out)
|
||||
share-links (:share-links result)]
|
||||
|
||||
;; Should not error
|
||||
(t/is (nil? err))
|
||||
|
||||
;; Should only see the share-link used for authentication
|
||||
(t/is (= 1 (count share-links)))
|
||||
(t/is (= link-a-id (:id (first share-links))))
|
||||
|
||||
;; Should NOT see Link B's token
|
||||
(t/is (not (some #(= link-b-id (:id %)) share-links)))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user