Plugin repositories become rows an administrator manages, not one environment variable #93

Closed
opened 2026-09-07 15:19:02 +00:00 by tiagoagueda · 0 comments
Owner

Observation

admin plugin page:
repos: internal (cannot be disable), oficial (can be enable/disable and customized, if it
is not on the env, like smtp), multiple repos that can be enable/disable/customized

plugins kinds: internal (shipped with postulo), oficial plugin (downloaded from official
repo, or thru a zip) custom plugin (downloaded from custom repo, or thru a zip)
all plugins can be enable and disable but a admin can impose enable/disable of a plugin on
any user

user-plugin:
can see what current plugins (either kind) are enable in their session

This is the first of four. The others are the plugin kinds shown on the page, the policy
that lets an administrator decide a plugin for a person, and the page where a person sees
what is running for them.

What exists

Catalogues already work, and rather well. plugins/catalogue.py fetches a signed index,
verifies Ed25519 against a configured public key, and refuses a wheel whose SHA-256 does not
match the one the signed index gave. Its docstring is worth keeping in view:

Without the key there is no catalogue: an unsigned list of URLs to run code from is not
something Postulo will offer.

Several catalogues are already supported. What is missing is any way to manage them:

POSTULO_PLUGIN_CATALOGUES = env("POSTULO_PLUGIN_CATALOGUES", default="")

name|url|public-key, comma-separated, parsed with str.split, empty by default. To add
one you edit a file and restart the container. There is no row, no page, no switch, and no
notion that one catalogue might be more trusted than another.

What this asks for

A PluginRepository row per catalogue, with a tier:

Tier Can be disabled Can be edited How many
Internal No No Exactly one
Official Yes Yes, unless the environment pins it Exactly one
Custom Yes Yes Any number

Internal is not a catalogue at all. It is the plugins that ship inside Postulo --
BUILTIN_SOURCES today -- shown as a repository so the page reads as one list rather than
two unrelated ones. It has no URL and no key because nothing is fetched: the code is already
in the image, and it arrived the same way the rest of Postulo did. It cannot be disabled
because disabling it would mean disabling Postulo.

The environment still wins, using the mechanism that already exists. site.ENV_OVERRIDES
maps a field to the variable that pins it and site.overridden_by() says whether one is set;
four settings already work this way and #84 extends it to SMTP. The official repository's URL
and key follow the same rule: pinned in the environment, they show their values, greyed out
and unwritable. Unpinned, they are editable. This should reuse that mechanism rather than
grow a second one
-- and since it needs a list rather than a single row, extending
ENV_OVERRIDES to cover it is part of the work, not a detail.

Four things to decide, and none of them is code

1. There is no official repository, and creating one is a commitment. Postulo does not
publish a catalogue. Doing so means generating a signing key, keeping it safe for the life of
the project, having a story for rotating it if it leaks, and deciding what "official" claims.
catalogue.py already answers the last part honestly and the page should keep saying it:

being listed means the people who publish that catalogue looked at it, which is a review
and not a guarantee

A key that lives on one person's laptop and signs code that runs inside other people's
instances is a real responsibility. It should be taken deliberately or not at all -- and
until it is, the official row can ship disabled and empty rather than pointing nowhere.

2. Changing a repository's public key is a security event, not an edit. The key is the
only thing standing between an index and arbitrary code. Editing it in a form beside the URL
makes it look like a preference. It needs a confirmation that says what is being given up,
and the change belongs in an audit trail.

3. What happens to plugins already installed from a repository that is switched off.
They keep working -- they are installed, the code is on the volume, and silently disabling
somebody's working notifier because a URL was toggled would be its own bug. What stops is
updating and installing from it. The page has to say that, or an administrator will assume
"off" means "gone".

4. Whether a custom repository may be added at all on an instance somebody else operates.
It is the point where an operator can introduce code into an application holding other
people's CVs. That is legitimate -- it is their server -- but it deserves the same plainness
the install page already uses.

Scope

  • PluginRepository model with the tier, enabled flag, URL and key; the internal row
    synthesised rather than stored.
  • catalogue.configured() reads rows and merges the environment over them, keeping its
    current shape so nothing downstream changes.
  • Server settings -> Plugins grows a repositories section: add, edit, enable, disable,
    remove a custom one; the official one editable only when the environment leaves it alone;
    the internal one shown and unswitchable.
  • A migration importing whatever POSTULO_PLUGIN_CATALOGUES currently holds, so an
    existing instance loses nothing.
  • tests/security/ for the refusals: no key, no repository; a disabled repository serves
    nothing; an environment-pinned field cannot be written through the form.

Classification

Enhancement, interface. Not breaking: the environment variable keeps working and keeps
winning.

## Observation > admin plugin page: > repos: internal (cannot be disable), oficial (can be enable/disable and customized, if it > is not on the env, like smtp), multiple repos that can be enable/disable/customized > > plugins kinds: internal (shipped with postulo), oficial plugin (downloaded from official > repo, or thru a zip) custom plugin (downloaded from custom repo, or thru a zip) > all plugins can be enable and disable but a admin can impose enable/disable of a plugin on > any user > > user-plugin: > can see what current plugins (either kind) are enable in their session This is the first of four. The others are the plugin kinds shown on the page, the policy that lets an administrator decide a plugin for a person, and the page where a person sees what is running for them. ## What exists Catalogues already work, and rather well. `plugins/catalogue.py` fetches a **signed** index, verifies Ed25519 against a configured public key, and refuses a wheel whose SHA-256 does not match the one the signed index gave. Its docstring is worth keeping in view: > Without the key there is no catalogue: an unsigned list of URLs to run code from is not > something Postulo will offer. **Several catalogues are already supported.** What is missing is any way to manage them: ```python POSTULO_PLUGIN_CATALOGUES = env("POSTULO_PLUGIN_CATALOGUES", default="") ``` `name|url|public-key`, comma-separated, parsed with `str.split`, empty by default. To add one you edit a file and restart the container. There is no row, no page, no switch, and no notion that one catalogue might be more trusted than another. ## What this asks for A `PluginRepository` row per catalogue, with a **tier**: | Tier | Can be disabled | Can be edited | How many | | --- | --- | --- | --- | | Internal | **No** | No | Exactly one | | Official | Yes | Yes, unless the environment pins it | Exactly one | | Custom | Yes | Yes | Any number | **Internal is not a catalogue at all.** It is the plugins that ship inside Postulo -- `BUILTIN_SOURCES` today -- shown as a repository so the page reads as one list rather than two unrelated ones. It has no URL and no key because nothing is fetched: the code is already in the image, and it arrived the same way the rest of Postulo did. It cannot be disabled because disabling it would mean disabling Postulo. **The environment still wins, using the mechanism that already exists.** `site.ENV_OVERRIDES` maps a field to the variable that pins it and `site.overridden_by()` says whether one is set; four settings already work this way and #84 extends it to SMTP. The official repository's URL and key follow the same rule: pinned in the environment, they show their values, greyed out and unwritable. Unpinned, they are editable. **This should reuse that mechanism rather than grow a second one** -- and since it needs a list rather than a single row, extending `ENV_OVERRIDES` to cover it is part of the work, not a detail. ## Four things to decide, and none of them is code **1. There is no official repository, and creating one is a commitment.** Postulo does not publish a catalogue. Doing so means generating a signing key, keeping it safe for the life of the project, having a story for rotating it if it leaks, and deciding what "official" claims. `catalogue.py` already answers the last part honestly and the page should keep saying it: > being listed means the people who publish that catalogue looked at it, which is a review > and not a guarantee A key that lives on one person's laptop and signs code that runs inside other people's instances is a real responsibility. It should be taken deliberately or not at all -- and until it is, the official row can ship disabled and empty rather than pointing nowhere. **2. Changing a repository's public key is a security event, not an edit.** The key is the only thing standing between an index and arbitrary code. Editing it in a form beside the URL makes it look like a preference. It needs a confirmation that says what is being given up, and the change belongs in an audit trail. **3. What happens to plugins already installed from a repository that is switched off.** They keep working -- they are installed, the code is on the volume, and silently disabling somebody's working notifier because a URL was toggled would be its own bug. What stops is updating and installing from it. The page has to say that, or an administrator will assume "off" means "gone". **4. Whether a custom repository may be added at all on an instance somebody else operates.** It is the point where an operator can introduce code into an application holding other people's CVs. That is legitimate -- it is their server -- but it deserves the same plainness the install page already uses. ## Scope - `PluginRepository` model with the tier, enabled flag, URL and key; the internal row synthesised rather than stored. - `catalogue.configured()` reads rows and merges the environment over them, keeping its current shape so nothing downstream changes. - *Server settings -> Plugins* grows a repositories section: add, edit, enable, disable, remove a custom one; the official one editable only when the environment leaves it alone; the internal one shown and unswitchable. - A migration importing whatever `POSTULO_PLUGIN_CATALOGUES` currently holds, so an existing instance loses nothing. - `tests/security/` for the refusals: no key, no repository; a disabled repository serves nothing; an environment-pinned field cannot be written through the form. ## Classification Enhancement, interface. Not breaking: the environment variable keeps working and keeps winning.
tiagoagueda added this to the 0.3.0 milestone 2026-09-07 15:19:02 +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.

Reference
Postulo/postulo#93
No description provided.