SEC2-070: CI installs uv with an unpinned curl | sh and audits no dependency, and no existing issue names this repository #15

Closed
opened 2026-10-17 10:40:04 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-10-16 in a read-only security review of postulo-templates at 5c55d4d, 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-40
  • pyproject.toml:48 (the dev group's core pin, @d28ef8384, 124 commits behind)

Issue

# ci.yml:47-50 and release.yml:37-40
- name: Install uv
  run: |
    curl -LsSf https://astral.sh/uv/install.sh | sh

Both workflows fetch the installer from an unversioned address and pipe it into a shell. The core
pins UV_VERSION, downloads the versioned installer, checks UV_INSTALLER_SHA256 with
sha256sum -c before running it (postulo/postulo#790), and gives the reason: "a lock file read by
an 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) against
requires-python = ">=3.12.4". postulo/postulo-apprise#13 filed the uv pin and the audit job for
six 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 export of the
lock, 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_VERSION equal to the core's and
UV_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-hashes and a pinned pip-audit --strict over it, on push and on a weekly cron. Repin the
dev group to the current core and relock in the same change.

Done when: no workflow contains astral.sh/uv/install.sh without a version and a checksum, a
deliberately wrong checksum fails the step, and a scheduled job audits the exported lock.

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.

Found on 2026-10-16 in a read-only security review of `postulo-templates` at `5c55d4d`, 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-40` - `pyproject.toml:48` (the dev group's core pin, `@d28ef8384`, 124 commits behind) ## Issue ```yaml # ci.yml:47-50 and release.yml:37-40 - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh ``` Both workflows fetch the installer from an unversioned address and pipe it into a shell. The core pins `UV_VERSION`, downloads the versioned installer, checks `UV_INSTALLER_SHA256` with `sha256sum -c` before running it (postulo/postulo#790), and gives the reason: "a lock file read by an 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`) against `requires-python = ">=3.12.4"`. postulo/postulo-apprise#13 filed the uv pin and the audit job for six 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 export` of the lock, 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_VERSION` equal to the core's and `UV_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-hashes` and a pinned `pip-audit --strict` over it, on push and on a weekly `cron`. Repin the dev group to the current core and relock in the same change. Done when: no workflow contains `astral.sh/uv/install.sh` without a version and a checksum, a deliberately 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.
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-templates#15
No description provided.