SEC2-070: CI installs uv with an unpinned curl | sh and audits no dependency, and no existing issue names this repository #15
Labels
No labels
bug
documentation
enhancement
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-templates#15
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 on 2026-10-16 in a read-only security review of
postulo-templatesat5c55d4d, checked by a second, independent pass.Severity: Low · Category: Configuration · Reference: CWE-494 (Download of Code Without Integrity Check); CWE-1104 (Use of Unmaintained Third Party Components); OWASP A08:2021 (Software and Data Integrity Failures)
Location:
.forgejo/workflows/ci.yml:47-50.forgejo/workflows/release.yml:37-40pyproject.toml:48(the dev group's core pin,@d28ef8384, 124 commits behind)Issue
Both workflows fetch the installer from an unversioned address and pipe it into a shell. The core
pins
UV_VERSION, downloads the versioned installer, checksUV_INSTALLER_SHA256withsha256sum -cbefore running it (postulo/postulo#790), and gives the reason: "a lock file read byan unpinned tool is pinned only in name" (postulo/postulo#233). Neither workflow here has an audit
step or a
schedule:trigger, and the suite runs on Python 3.14 only (ci.yml:55) againstrequires-python = ">=3.12.4". postulo/postulo-apprise#13 filed the uv pin and the audit job forsix plugin repositories on 2026-09-29; this repository became a plugin on 2026-10-11 and is not on
its list, and #13 predates the checksum step.
The lock itself is clean today: pip-audit 2.10.1, run on 2026-10-16 against
uv exportof thelock, finds no known vulnerability (pyjwt 2.15.1, urllib3 2.8.0, oauthlib 4.0.0). The dev group's
core pin is 124 commits behind, which matters only at the next relock.
Impact
A substituted installer runs in CI; a uv release that reads the lock differently changes what CI
installs on a commit that changed nothing; and a vulnerability in anything the lock carries would
reach a release with nothing reporting it. The CI token is read-only
(
permissions: contents: read), so a replaced installer gets no write.Fix
In both workflows, match the core as it is now:
UV_VERSIONequal to the core's andUV_INSTALLER_SHA256, download the versioned installer to a file,sha256sum -c, then run it.Add a job that runs
uv export --no-emit-project --all-groups --format requirements-txt --no-hashesand a pinnedpip-audit --strictover it, on push and on a weeklycron. Repin thedev group to the current core and relock in the same change.
Done when: no workflow contains
astral.sh/uv/install.shwithout a version and a checksum, adeliberately wrong checksum fails the step, and a scheduled job audits the exported lock.
Related
postulo/postulo-apprise#13 (open): the same weakness for apprise, paperless, helloworld, imap, dav
and mcp, without the checksum step; this repository is not named. #1 (open) ("Make
postulo-templates an installable plugin ... CI") is the umbrella and mentions neither the
installer nor an audit.