Skip to content

fix(pluginBridge): ask before installing from a zotero://plugin link - #104

Closed
iliasbeshimov wants to merge 1 commit into
windingwind:mainfrom
iliasbeshimov:fix/plugin-bridge-confirm
Closed

iliasbeshimov wants to merge 1 commit into
windingwind:mainfrom
iliasbeshimov:fix/plugin-bridge-confirm

Conversation

@iliasbeshimov

Copy link
Copy Markdown

What this changes

PluginBridge installs whatever add-on a zotero://plugin/?action=install&url=… link points to, without asking. This PR makes it:

  • accept only https:// addresses;
  • ask first, with a dialog that shows the full address (Cancel is the default button);
  • bump PluginBridge.version from 1 to 2. Only the highest version is registered globally, so one plugin built with this release replaces the version 1 handler that plugins built with older releases put in place.

The bridge's purpose and API are unchanged. I've kept the change small, since the class is already @deprecated and slated for removal. Removing it, or registering it only when a developer opts in, would also fix this; happy to change the PR to either if you prefer.

Why

The handler is global: if any installed plugin registers it (toolkit 2.2.8 and later, including 5.2.0), any zotero://plugin link reaches it. We tested this in Zotero 10.0.3 with Translate for Zotero 2.4.7 installed:

A link in a Zotero note Before With this PR
Plain click Zotero's link popup shows the address; nothing runs Same
Click the address in that popup Installs at once, no prompt Asks first; Cancel installs nothing
Ctrl+click the link Installs at once, no prompt Asks first; Cancel installs nothing

A link another app hands to Zotero (an OS-level zotero:// link) also reaches the handler through ZoteroPane.loadURI without a prompt from Zotero. Browsers usually ask "Open Zotero?" first, but that is all that stands between a web page and an install. A note can come from a shared group library or from another plugin, so the user may not have written the link. Links on a PDF page didn't trigger it in our test: the reader ignored the zotero:// link annotations.

We don't know of anyone abusing this. We found it while checking Zotero plugins for zoteroplugins.org, a directory that grades plugins from their code: our notes, D37, D40 and D42.

Testing

  • npm run build passes, and so do Prettier and ESLint (the pre-commit hooks).
  • Zotero 10.0.3 (Linux ARM64, in a throwaway sandbox). A test plugin bundling this branch's PromptManager was installed beside the unpatched Translate for Zotero, and links in a note were clicked with real mouse events. The version 2 handler replaced the old one. Both routes in the table showed the dialog with the address: OK installed, Cancel installed nothing. The install address was on .invalid, so nothing was downloaded.
  • Not covered: plain http:// and other schemes, which are refused before the dialog by design.

Related, not in this PR

DebugBridge already asks before running code. Its dialog shows the app name taken from the link and only the first 100 characters of the command, so a link can make it look harmless. Also, disableDebugBridgePassword set by any one plugin turns the prompt off for every plugin. Showing the whole command, and making that setting apply only to the plugin that sets it, would close that off. I can open a separate PR if that's useful.

🤖 Generated with Claude Code

The plugin bridge installed any add-on a `zotero://plugin/?action=install&url=...` link
pointed to, without asking. Such a link reaches the handler from a note (clicking the
address in Zotero's link popup, or Ctrl+click) or from another app, so the user may not
have chosen it. Now it accepts only https:// addresses and asks first, showing the
address, with Cancel as the default button.

The bridge version goes from 1 to 2: only the highest version is registered, so a
plugin built with this release replaces version 1 handlers that plugins built with
older releases registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@windingwind

Copy link
Copy Markdown
Owner

Please do not. This is only for debugging purposes and should never be included in any plugin that is distributed for users

@windingwind windingwind closed this Oct 1, 2026
@iliasbeshimov

Copy link
Copy Markdown
Author

Thanks, agreed it shouldn't ship to users. The catch is that plugin authors aren't opting in: initializeModules() in src/managers/toolkitGlobal.ts calls PluginBridge.setModule() whenever a plugin first uses PromptManager or basicOptions.debug, so it ends up in release builds without anyone choosing it.

I checked the latest releases of the 1,123 Zotero plugins I track: 389 of them register the zotero://plugin handler, and those releases have about 4 million downloads on GitHub. That includes Better Notes, Translate for Zotero and Actions and Tags, and zotero-plugin-template, so new plugins started from the template get it too.

Would you take a small PR that drops that call, so the bridge is only registered when a developer sets it up explicitly? Or one that removes PluginBridge entirely, since it's deprecated. Either keeps it out of user builds.

@windingwind

Copy link
Copy Markdown
Owner

Thanks. I removed those modules from the latest release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants