PST-061: CI and release workflows trust mutable references and unreviewed packages, and publish unsigned artefacts #790

Closed
opened 2026-10-10 10:56:32 +00:00 by tiagoagueda · 0 comments
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 8 to 10 October 2026, as PST-061. 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.


  • Severity: Low
  • Category: supply chain / CI
  • Location: .forgejo/workflows/ci.yml (66,79-80,83,154); .forgejo/workflows/dependencies.yml (63,78,91,105,119); .forgejo/workflows/release.yml (64-75)
  • Found by: configuration and dependencies

Who can do it, and what they need. Whoever can move a tag or replace a download: the Forgejo instance's mirror of actions/checkout, actions/cache or actions/upload-artifact; astral.sh's install.sh; the ghcr.io uv image tag. Precondition: compromise of one of those upstreams.

How it works.

  1. Every workflow uses actions/checkout@v4, actions/cache@v4 and actions/upload-artifact@v3 by mutable tag; zizmor's unpinned-uses rule is relaxed to ref-pin for actions/* (zizmor.yml:14-17) because Forgejo resolves them locally.
  2. uv is installed with curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh with no checksum, and the image copies uv from ghcr.io/astral-sh/uv:0.12.17 by tag rather than digest.
  3. A replaced action, script or image runs with the job's credentials: read-only for CI, but release.yml and image.yml carry contents:write and registry tokens.

Impact. A compromised upstream could alter the published sdist/wheel or container image. The reasons for each choice are documented (Forgejo action resolution, deliberate Debian drift); the uv binary and install script are the two places a digest or checksum would cost nothing.

Evidence.

ci.yml:79-80 `curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh`; Dockerfile:50 `COPY --from=ghcr.io/astral-sh/uv:0.12.17 /uv /usr/local/bin/uv`; zizmor.yml:14-17 `unpinned-uses: config: policies: "actions/*": ref-pin`.

Verification (verdict: confirmed; severity after verification: Low; first-pass claim: Informational). Attacker is an upstream party only: the astral.sh host, the astral-sh GHCR namespace, or whoever controls the Forgejo instance's actions/* mirror (the tag-moving party). Corrections: (a) the second uv COPY is Dockerfile:147, which is the runtime stage that ships, so a tampered uv binary would reach the published image directly; (b) the release job's token is exported to all steps (ci.yml:587-590), so the installer step sees it despite no step-level env; (c) ci.yml:8-11 still says 'Only actions/checkout is used', which is stale (cache and upload-artifact are used too) and zizmor.yml:3-4 names only checkout and upload-artifact while the policy covers all actions/*. Remediation: pin the uv image by digest (uv:0.12.17@sha256:...) in both Dockerfile COPY lines and bump it with the version; download the uv release archive and check its SHA-256 (or a checked-in installer hash) instead of curl | sh; where the Forgejo mirror allows it, pin actions to a commit SHA.

Suggested remediation. Verify install.sh (or the uv tarball) against a pinned sha256 before running it; pin the uv image by digest in the Dockerfile (the tag is already a patch version, so a digest is one line); mirror the three actions at a commit on the Forgejo instance and pin to that commit.

Also covered by this issue.

  • The monthly dependency workflow executes code from freshly resolved, unreviewed package versions (.forgejo/workflows/dependencies.yml (63,78,91,105,119); Informational). Code execution on the CI runner with no write credential in scope; a well-scoped design. Recorded so the synthesis can note the token scoping is what makes it safe, and that --force would overwrite a human's branch of the same name.
  • Release artefacts and container images are published unsigned (.forgejo/workflows/release.yml (64-75); Informational). No way for operators to verify provenance of what they pull; a registry compromise would be invisible.
Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 8 to 10 October 2026, as **PST-061**. 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. --- - **Severity:** Low - **Category:** supply chain / CI - **Location:** `.forgejo/workflows/ci.yml` (66,79-80,83,154); `.forgejo/workflows/dependencies.yml` (63,78,91,105,119); `.forgejo/workflows/release.yml` (64-75) - **Found by:** configuration and dependencies **Who can do it, and what they need.** Whoever can move a tag or replace a download: the Forgejo instance's mirror of actions/checkout, actions/cache or actions/upload-artifact; astral.sh's install.sh; the ghcr.io uv image tag. Precondition: compromise of one of those upstreams. **How it works.** 1. Every workflow uses `actions/checkout@v4`, `actions/cache@v4` and `actions/upload-artifact@v3` by mutable tag; zizmor's unpinned-uses rule is relaxed to ref-pin for actions/* (zizmor.yml:14-17) because Forgejo resolves them locally. 2. uv is installed with `curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh` with no checksum, and the image copies uv from `ghcr.io/astral-sh/uv:0.12.17` by tag rather than digest. 3. A replaced action, script or image runs with the job's credentials: read-only for CI, but release.yml and image.yml carry contents:write and registry tokens. **Impact.** A compromised upstream could alter the published sdist/wheel or container image. The reasons for each choice are documented (Forgejo action resolution, deliberate Debian drift); the uv binary and install script are the two places a digest or checksum would cost nothing. **Evidence.** ```text ci.yml:79-80 `curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh`; Dockerfile:50 `COPY --from=ghcr.io/astral-sh/uv:0.12.17 /uv /usr/local/bin/uv`; zizmor.yml:14-17 `unpinned-uses: config: policies: "actions/*": ref-pin`. ``` **Verification** (verdict: confirmed; severity after verification: Low; first-pass claim: Informational). Attacker is an upstream party only: the astral.sh host, the astral-sh GHCR namespace, or whoever controls the Forgejo instance's actions/* mirror (the tag-moving party). Corrections: (a) the second uv COPY is Dockerfile:147, which is the runtime stage that ships, so a tampered uv binary would reach the published image directly; (b) the release job's token is exported to all steps (ci.yml:587-590), so the installer step sees it despite no step-level `env`; (c) ci.yml:8-11 still says 'Only actions/checkout is used', which is stale (cache and upload-artifact are used too) and zizmor.yml:3-4 names only checkout and upload-artifact while the policy covers all actions/*. Remediation: pin the uv image by digest (`uv:0.12.17@sha256:...`) in both Dockerfile COPY lines and bump it with the version; download the uv release archive and check its SHA-256 (or a checked-in installer hash) instead of `curl | sh`; where the Forgejo mirror allows it, pin actions to a commit SHA. **Suggested remediation.** Verify install.sh (or the uv tarball) against a pinned sha256 before running it; pin the uv image by digest in the Dockerfile (the tag is already a patch version, so a digest is one line); mirror the three actions at a commit on the Forgejo instance and pin to that commit. **Also covered by this issue.** - **The monthly dependency workflow executes code from freshly resolved, unreviewed package versions** (`.forgejo/workflows/dependencies.yml` (63,78,91,105,119); Informational). Code execution on the CI runner with no write credential in scope; a well-scoped design. Recorded so the synthesis can note the token scoping is what makes it safe, and that `--force` would overwrite a human's branch of the same name. - **Release artefacts and container images are published unsigned** (`.forgejo/workflows/release.yml` (64-75); Informational). No way for operators to verify provenance of what they pull; a registry compromise would be invisible.
tiagoagueda added this to the 0.6.0 milestone 2026-10-10 10:58:00 +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#790
No description provided.