PST-054: The signed plugin catalogue index has no freshness or version counter, so an old signed index can be replayed #783
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#783
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-054. Severity: Low.The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.
src/postulo/plugins/catalogue.py(185-241; 229-245,310-340,392-430)Who can do it, and what they need. A man-in-the-middle on the catalogue URL or whoever controls the hosting/CDN of the index (not the signing key). Precondition: a catalogue is configured and an administrator presses Refresh/Install, or
plugins syncruns at container start.How it works.
plugins; find() returns the old 'latest'.plugins syncreinstalls, the withdrawn release.Impact. Rollback to a signed-but-withdrawn plugin version; cannot introduce unsigned code. Low because the publisher must previously have signed the bad release.
Evidence.
Verification (verdict: confirmed; severity after verification: Low). Drop the claim about
plugins sync: it is version-pinned to the already-installed version (commands/plugins.py:181, #228), so a replay cannot make it install anything new. The attack applies to the administrator's Refresh/Install in Server settings -> Plugins (server_views.py _refresh/_install -> catalogue.fetch/install), plus the digest cache that remember() overwrites with the old index's digests (cosmetic: it only changes the 'from repository' label on uploads). Attacker: whoever controls the index hosting without the signing key, or a MITM if the configured index URL is http://. Remediation: add a signedgenerated/expirestimestamp or a monotonically increasing serial, remember the highest serial seen per repository, and refuse an index that is older or expired. Ayanked/withdrawnflag per release would also help.Suggested remediation. Add
generated_at,expiresand a monotonically increasingserialto the signed index; refuse an index past its expiry or with a serial lower than the last one remembered per repository (kept_digests already stores per-repository state in the cache and could hold the serial). This is the TUF 'timestamp/snapshot' idea in one field.Also covered by this issue.
src/postulo/plugins/catalogue.py(229-245,310-340,392-430); Low). Downgrade attack on plugin code that the signature scheme is described as preventing ('a modified index cannot ship code'). Limited to releases the catalogue ever signed.Fixed in
c077d02bfon main.