SEC2-068: CI installs uv from the moving install.sh with no version and no checksum; the core fixed this in #790 #20

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

Found on 2026-10-16 in a read-only security review of postulo-mcp at 3819dbd, checked by a second, independent pass.

Severity: Low · Category: Configuration · Reference: CWE-829 (Inclusion of Functionality from Untrusted Control Sphere); OWASP A08:2021 (Software and Data Integrity Failures)

Location:

  • .forgejo/workflows/ci.yml:19-24 (curl -LsSf https://astral.sh/uv/install.sh | sh)

Issue

- name: Install uv
  run: |
    apt-get update && apt-get install -y --no-install-recommends curl
    curl -LsSf https://astral.sh/uv/install.sh | sh

The installer is fetched from an unversioned address and piped straight into a shell on every
run, so whatever astral.sh (or anything able to answer for it) serves that day runs in the job
with the checkout and the dependency resolution. The core closed this in postulo/postulo#790:
its ci.yml pins UV_VERSION, downloads https://astral.sh/uv/${UV_VERSION}/install.sh to a
file, checks UV_INSTALLER_SHA256 with sha256sum -c and only then runs it
(postulo/.forgejo/workflows/ci.yml:88-90).

Impact

A compromised or substituted installer runs arbitrary code in CI for every push and pull request.
The job has only the repository (no publishing step, no secrets beyond the checkout token), so the
reach is the integrity of the test and lint verdict and of anything the runner caches, not a
published artefact.

Fix

Copy the core's step:

env:
  UV_VERSION: "<x.y.z, as the core>"
  UV_INSTALLER_SHA256: "<sha256 of that installer>"
...
run: |
  curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" -o /tmp/uv-install.sh
  echo "${UV_INSTALLER_SHA256}  /tmp/uv-install.sh" | sha256sum -c -
  sh /tmp/uv-install.sh

#8 rewrites the same job (Node image, uv sync --locked), so the pinned installer belongs in that
change.

Done when the workflow runs green with the pinned installer and a deliberately wrong sha fails the
step.

#8 (open) covers the job image, uv sync --locked and testing one Python only; it does not
mention how uv is installed. postulo/postulo#790 (closed) is the core's fix of the same thing;
postulo/postulo-imap#14 (open) is the imap repository's.

Found on 2026-10-16 in a read-only security review of `postulo-mcp` at `3819dbd`, checked by a second, independent pass. **Severity:** Low · **Category:** Configuration · **Reference:** CWE-829 (Inclusion of Functionality from Untrusted Control Sphere); OWASP A08:2021 (Software and Data Integrity Failures) **Location:** - `.forgejo/workflows/ci.yml:19-24` (`curl -LsSf https://astral.sh/uv/install.sh | sh`) ## Issue ```yaml - name: Install uv run: | apt-get update && apt-get install -y --no-install-recommends curl curl -LsSf https://astral.sh/uv/install.sh | sh ``` The installer is fetched from an unversioned address and piped straight into a shell on every run, so whatever astral.sh (or anything able to answer for it) serves that day runs in the job with the checkout and the dependency resolution. The core closed this in postulo/postulo#790: its `ci.yml` pins `UV_VERSION`, downloads `https://astral.sh/uv/${UV_VERSION}/install.sh` to a file, checks `UV_INSTALLER_SHA256` with `sha256sum -c` and only then runs it (`postulo/.forgejo/workflows/ci.yml:88-90`). ## Impact A compromised or substituted installer runs arbitrary code in CI for every push and pull request. The job has only the repository (no publishing step, no secrets beyond the checkout token), so the reach is the integrity of the test and lint verdict and of anything the runner caches, not a published artefact. ## Fix Copy the core's step: ```yaml env: UV_VERSION: "<x.y.z, as the core>" UV_INSTALLER_SHA256: "<sha256 of that installer>" ... run: | curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" -o /tmp/uv-install.sh echo "${UV_INSTALLER_SHA256} /tmp/uv-install.sh" | sha256sum -c - sh /tmp/uv-install.sh ``` #8 rewrites the same job (Node image, `uv sync --locked`), so the pinned installer belongs in that change. Done when the workflow runs green with the pinned installer and a deliberately wrong sha fails the step. ## Related #8 (open) covers the job image, `uv sync --locked` and testing one Python only; it does not mention how uv is installed. postulo/postulo#790 (closed) is the core's fix of the same thing; postulo/postulo-imap#14 (open) is the imap repository's.
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-mcp#20
No description provided.