Installing a plugin fetches its dependencies from PyPI, unsigned and unpinned #61

Closed
opened 2026-09-06 16:05:55 +00:00 by tiagoagueda · 0 comments
Owner

What is and is not checked today

The plugin chain is careful about the plugin itself, and that part is good:

  • the catalogue index carries an Ed25519 signature, checked against the configured key;
  • each release names a SHA-256, and install_wheel refuses a wheel that does not match;
  • check() refuses anything that would change what Postulo itself depends on, enforced
    with a constraint file passed to the installer.

Then run_install builds:

uv pip install --target <plugins> --constraint <constraints> --upgrade <wheel>

No --no-deps. No --only-binary. No hashes for anything but the plugin's own wheel. So
installing a signed, checksummed plugin resolves its requirements from PyPI and
installs whatever is served — and a source distribution among them runs its own build code
during the install, as the maintainer's user, inside Postulo's container.

Why this is worth an issue rather than a shrug

It is not a flaw in the signing; it is the edge of what the signing covers, and the
documentation does not currently say where that edge is. An administrator reading
"signature verified, checksum verified" in the interface reasonably concludes that
everything installed was verified. What was verified is one file out of however many
arrive.

The constraint file is a real protection and should be credited: a dependency cannot
downgrade Django or swap out cryptography. It says nothing about a package Postulo has
never heard of.

Shape, in the order that helps most per unit of work

  1. Say so. Plugins and Hardening state plainly that installing a plugin installs
    its dependencies from PyPI, and that trusting a plugin means trusting its dependency
    list. The confirmation before installing shows the requirements the wheel declares.
  2. --only-binary :all:, so nothing executes a build script during installation. A
    plugin that genuinely needs a source build is a plugin an operator should install by
    hand, deliberately.
  3. Show what actually landed: the installed record already exists, so record the
    dependencies that came with it and list them on the plugin's page. Then an operator can
    see what is in their instance without a shell.
  4. A locked catalogue, later. A catalogue entry could carry the full resolved set with
    hashes, and the install could be --require-hashes. That is the real fix, it is a
    change to the catalogue format, and it should not hold up the three above.

Classification

Security and enhancement. Not breaking; --only-binary could refuse a plugin that
installs today, which is the point, and the message should say what to do about it.

## What is and is not checked today The plugin chain is careful about the plugin itself, and that part is good: - the catalogue index carries an Ed25519 signature, checked against the configured key; - each release names a SHA-256, and `install_wheel` refuses a wheel that does not match; - `check()` refuses anything that would change what Postulo itself depends on, enforced with a constraint file passed to the installer. Then `run_install` builds: ``` uv pip install --target <plugins> --constraint <constraints> --upgrade <wheel> ``` No `--no-deps`. No `--only-binary`. No hashes for anything but the plugin's own wheel. So installing a signed, checksummed plugin resolves **its** requirements from PyPI and installs whatever is served — and a source distribution among them runs its own build code during the install, as the maintainer's user, inside Postulo's container. ## Why this is worth an issue rather than a shrug It is not a flaw in the signing; it is the edge of what the signing covers, and the documentation does not currently say where that edge is. An administrator reading "signature verified, checksum verified" in the interface reasonably concludes that everything installed was verified. What was verified is one file out of however many arrive. The constraint file is a real protection and should be credited: a dependency cannot downgrade Django or swap out cryptography. It says nothing about a package Postulo has never heard of. ## Shape, in the order that helps most per unit of work 1. **Say so.** *Plugins* and *Hardening* state plainly that installing a plugin installs its dependencies from PyPI, and that trusting a plugin means trusting its dependency list. The confirmation before installing shows the requirements the wheel declares. 2. **`--only-binary :all:`**, so nothing executes a build script during installation. A plugin that genuinely needs a source build is a plugin an operator should install by hand, deliberately. 3. **Show what actually landed**: the installed record already exists, so record the dependencies that came with it and list them on the plugin's page. Then an operator can see what is in their instance without a shell. 4. **A locked catalogue, later.** A catalogue entry could carry the full resolved set with hashes, and the install could be `--require-hashes`. That is the real fix, it is a change to the catalogue format, and it should not hold up the three above. ## Classification Security and enhancement. Not breaking; `--only-binary` could refuse a plugin that installs today, which is the point, and the message should say what to do about it.
tiagoagueda added this to the 0.2.0 milestone 2026-09-06 16:05:55 +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#61
No description provided.