Server settings → Plugins: Install takes the first catalogue listing the name, not the row that was pressed #601

Closed
opened 2026-10-01 10:48:11 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the catalogue refresh, the install form and catalogue.install.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at core/server_views.py:1438-1453 (_install, the call at :1444); the template, plugins/catalogue.py and plugins/models.py:66 have not moved.

After Check for updates, the page shows one row per catalogue per plugin, each tagged with its catalogue's name. The Install/Reinstall form posts only the plugin's name, and catalogue.install(name) installs whatever find() returns from the first configured catalogue that lists that name. Catalogue rows are ordered by (tier, name), and custom sorts before official, so a custom catalogue's package of the same name always wins over the official one, and among custom catalogues the alphabetically first wins.

  • Where: templates/server/plugins.html:408-413 (the form: hidden name and action only), core/server_views.py:1438-1452 (_install: catalogue.install(name, ...)), plugins/catalogue.py:321-341 (install), plugins/catalogue.py:262-287 (find, first listing in catalogue order), plugins/catalogue.py:237-245 (fetch_all, in configured() order) and plugins/models.py:66 (ordering = ("tier", "name")).
  • What happens: an instance has two catalogues that both list postulo-imap. The administrator presses Install, or Reinstall on an installed plugin, on the row labelled with one catalogue. Postulo downloads and installs the other catalogue's wheel, signed with that catalogue's key, and records origin as catalogue:<other>. The compatibility shown on the pressed row is also not the one checked. Only the provenance mark afterwards reveals the swap.
  • Who can trigger it: the substitution happens to an administrator who configured more than one catalogue; whoever runs the catalogue that sorts first decides which code is installed for a name another catalogue also lists.
  • Why it matters: choosing a publisher is the point of showing the catalogue on each row; the instance runs code from a publisher the administrator did not choose, including replacing an installed plugin with another publisher's build on Reinstall.
  • Fix: post the catalogue name and the version shown with the form, give catalogue.install() and find() catalogue= and version= parameters that fetch and search only that catalogue, and refuse when that catalogue no longer lists that release. Do the same for the command (plugins install <name>), which should refuse an ambiguous name unless a catalogue is given.

Done when

  • tests/test_plugin_install.py has a test with two catalogues listing the same name in which installing with catalogue=<the second> installs the second's wheel and records origin catalogue:<the second>, and a view test that the install form posts the catalogue.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the catalogue refresh, the install form and `catalogue.install`. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `core/server_views.py:1438-1453` (`_install`, the call at `:1444`); the template, `plugins/catalogue.py` and `plugins/models.py:66` have not moved. After *Check for updates*, the page shows one row per catalogue per plugin, each tagged with its catalogue's name. The *Install*/*Reinstall* form posts only the plugin's name, and `catalogue.install(name)` installs whatever `find()` returns from the first configured catalogue that lists that name. Catalogue rows are ordered by `(tier, name)`, and `custom` sorts before `official`, so a custom catalogue's package of the same name always wins over the official one, and among custom catalogues the alphabetically first wins. - **Where:** `templates/server/plugins.html:408-413` (the form: hidden `name` and `action` only), `core/server_views.py:1438-1452` (`_install`: `catalogue.install(name, ...)`), `plugins/catalogue.py:321-341` (`install`), `plugins/catalogue.py:262-287` (`find`, first listing in catalogue order), `plugins/catalogue.py:237-245` (`fetch_all`, in `configured()` order) and `plugins/models.py:66` (`ordering = ("tier", "name")`). - **What happens:** an instance has two catalogues that both list `postulo-imap`. The administrator presses *Install*, or *Reinstall* on an installed plugin, on the row labelled with one catalogue. Postulo downloads and installs the other catalogue's wheel, signed with that catalogue's key, and records `origin` as `catalogue:<other>`. The compatibility shown on the pressed row is also not the one checked. Only the provenance mark afterwards reveals the swap. - **Who can trigger it:** the substitution happens to an administrator who configured more than one catalogue; whoever runs the catalogue that sorts first decides which code is installed for a name another catalogue also lists. - **Why it matters:** choosing a publisher is the point of showing the catalogue on each row; the instance runs code from a publisher the administrator did not choose, including replacing an installed plugin with another publisher's build on *Reinstall*. - **Fix:** post the catalogue name and the version shown with the form, give `catalogue.install()` and `find()` `catalogue=` and `version=` parameters that fetch and search only that catalogue, and refuse when that catalogue no longer lists that release. Do the same for the command (`plugins install <name>`), which should refuse an ambiguous name unless a catalogue is given. ## Done when - `tests/test_plugin_install.py` has a test with two catalogues listing the same name in which installing with `catalogue=<the second>` installs the second's wheel and records `origin` `catalogue:<the second>`, and a view test that the install form posts the catalogue.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:48:11 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#601
No description provided.