From e6bba5785f8dd91e4e71d041371450b7030a0aac Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 30 Jul 2026 09:54:57 +0000 Subject: [PATCH] :sparkles: Require re-confirmation when plugin manifest differs on open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the fetched manifest differs from the stored manifest (code, name, etc.), show the permissions dialog instead of silently updating — preventing execution of tampered/injected plugins. On fetch error, show a warning notification instead of loading with the old manifest. Bundled plugins (no URL) skip validation as they are trusted with no remote source. Completes the 3-layer defense for T3-N1-02: (1) closed permission schema, (2) dedicated RPC methods, (3) integrity validation on open. AI-assisted-by: qwen3.7-plus --- backend/src/app/rpc/commands/profile.clj | 3 ++- frontend/src/app/main/data/plugins.cljs | 26 +++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index a06d42262c..8cde343655 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -12,6 +12,7 @@ [app.common.exceptions :as ex] [app.common.schema :as sm] [app.common.time :as ct] + [app.common.types.plugins :as ctp] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] @@ -57,7 +58,7 @@ (def schema:props [:map {:title "ProfileProps" :closed true} - [:plugins {:optional true} schema:plugin-registry] + [:plugins {:optional true} ctp/schema:plugin-registry] [:renderer {:optional true} [::sm/one-of #{:svg :wasm}]] [:mcp-enabled {:optional true} ::sm/boolean] [:newsletter-updates {:optional true} ::sm/boolean] diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index 4f7e8b7ceb..f6d7fc2ca0 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -115,7 +115,7 @@ [{:keys [url] :as manifest} user-can-edit?] (if url ;; If the saved manifest has a URL we fetch the manifest to check - ;; for updates + ;; for updates and validate integrity (->> (fetch-manifest url) (rx/subs! (fn [new-manifest] @@ -127,6 +127,8 @@ (cond (and is-edition-plugin? (not user-can-edit?)) (st/emit! (ntf/warn (tr "workspace.plugins.error.need-editor"))) + + ;; Permissions changed - show permissions dialog (not= (:permissions new-manifest) (:permissions manifest)) (modal/show! :plugin-permissions-update @@ -136,15 +138,25 @@ (preg/install-plugin! new-manifest) (load-plugin! new-manifest))}) + ;; Manifest changed (code, name, etc.) - require re-confirmation + ;; This prevents execution of tampered/injected plugins (not= new-manifest manifest) - (do (preg/install-plugin! new-manifest) - (load-plugin! manifest)) + (modal/show! + :plugin-permissions-update + {:plugin new-manifest + :on-accept + #(do + (preg/install-plugin! new-manifest) + (load-plugin! new-manifest))}) + + ;; Manifests match exactly - safe to load :else (load-plugin! manifest)))) - (fn [] - ;; Error fetching the manifest we'll load the plugin with the - ;; old manifest - (load-plugin! manifest)))) + (fn [_err] + ;; Error fetching the manifest - can't verify integrity + ;; Show error instead of loading potentially tampered code + (st/emit! (ntf/warn (tr "workspace.plugins.error.unreachable")))))) + ;; Bundled plugins (no URL) - trusted, load directly (load-plugin! manifest))) (defn close-plugin!