Repo signing signs whatever is in the package registry #40

Closed
opened 2026-09-26 07:28:39 +08:00 by Breadway · 3 comments
Owner

Problem

scripts/ci-publish-signed-repo.sh collect_from_registry (line 229) downloads every package from the Forgejo package registry and signs it with repo-add -s, with no provenance check. Anyone who can write to that registry gets their package signed and pushed to all installs.

Fix

  • Keep an allowlist that includes the expected pkgver per package, plus sha256 values recorded by the build workflow that produced each package (e.g. a build-provenance JSON committed or attested per package), and refuse to sign anything that doesn't match.
  • Or build and sign in the same job.

Acceptance

Upload a tampered package to the registry, and the publish job refuses to sign it.


Source: BOS v0.6.2 source review (tag dated 2026-09-19); line numbers refer to that tree. Read AGENTS.md first. One issue per branch/PR.

## Problem `scripts/ci-publish-signed-repo.sh` `collect_from_registry` (line 229) downloads every package from the Forgejo package registry and signs it with `repo-add -s`, with no provenance check. Anyone who can write to that registry gets their package signed and pushed to all installs. ## Fix - Keep an allowlist that includes the expected `pkgver` per package, plus sha256 values recorded by the build workflow that produced each package (e.g. a build-provenance JSON committed or attested per package), and refuse to sign anything that doesn't match. - Or build and sign in the same job. ## Acceptance Upload a tampered package to the registry, and the publish job refuses to sign it. --- Source: BOS v0.6.2 source review (tag dated 2026-09-19); line numbers refer to that tree. Read AGENTS.md first. One issue per branch/PR.
Author
Owner

Investigation found no trusted package provenance source to validate against. scripts/ci-publish-signed-repo.sh parses name/filename from the registry database, then signs the downloaded registry bytes. The five BOS package workflows (calamares, bibata, powerlevel10k, yay-bin, python-pywal) only PUT the package and do not emit a checksum attestation. breadlock and zen-browser-bin are also in the signer allowlist but have no package build workflow in this repository. The registry metadata and .PKGINFO are supplied by the same upload, so checking name/version there cannot detect a same-version tampered package.

A checked-in pkgver/SHA256 allowlist would reject the tampered upload, but there is no trusted digest update path today and it would need to be refreshed for every rebuilt package. Forgejo v15 also does not support retrieving artifacts from other workflow runs, so the existing workflow_run trigger cannot safely collect prior run artifacts directly.

Recommended direction: establish a trusted per-package provenance channel for all seven packages (including breadlock and zen-browser-bin), bound to the producing workflow/ref, then make the signer fail closed unless both the expected package name/version and SHA256 match that attestation. The provenance must be outside the attacker-writable Arch package registry or cryptographically authenticated with a separate trust anchor. I left BOS-40 in TODO pending that design/configuration decision, without opening a speculative PR.

Investigation found no trusted package provenance source to validate against. `scripts/ci-publish-signed-repo.sh` parses name/filename from the registry database, then signs the downloaded registry bytes. The five BOS package workflows (calamares, bibata, powerlevel10k, yay-bin, python-pywal) only PUT the package and do not emit a checksum attestation. `breadlock` and `zen-browser-bin` are also in the signer allowlist but have no package build workflow in this repository. The registry metadata and `.PKGINFO` are supplied by the same upload, so checking name/version there cannot detect a same-version tampered package. A checked-in `pkgver`/SHA256 allowlist would reject the tampered upload, but there is no trusted digest update path today and it would need to be refreshed for every rebuilt package. Forgejo v15 also does not support retrieving artifacts from other workflow runs, so the existing `workflow_run` trigger cannot safely collect prior run artifacts directly. Recommended direction: establish a trusted per-package provenance channel for all seven packages (including breadlock and zen-browser-bin), bound to the producing workflow/ref, then make the signer fail closed unless both the expected package name/version and SHA256 match that attestation. The provenance must be outside the attacker-writable Arch package registry or cryptographically authenticated with a separate trust anchor. I left BOS-40 in TODO pending that design/configuration decision, without opening a speculative PR.
Author
Owner

PR: #78

Implemented protected exact-version/SHA256 allowlisting and build-candidate provenance, stacked on BOS-39 PR #75. Registry metadata no longer chooses signed packages. Same-version tampered registry downloads, local overrides and offline sign-only input fail before signatures; valid packages get real throwaway-key pacman package/database signatures. The prior published tree remains intact on failure.

Verified the committed bootstrap database/signature against pinned repository key 56203B86A110695AE7F310934AF3323D678EB5E2. Tests independently recheck that evidence, strict file/identity/policy validation, provenance recording, YAML/shell blocks, Python compilation and diff checks. New build entries require reviewed protected-main policy advancement, including the separate breadlock producer. No production registry tampering, live configs, desktop or keys were used.

PR: https://git.breadway.dev/Breadway/bos/pulls/78 Implemented protected exact-version/SHA256 allowlisting and build-candidate provenance, stacked on BOS-39 PR #75. Registry metadata no longer chooses signed packages. Same-version tampered registry downloads, local overrides and offline sign-only input fail before signatures; valid packages get real throwaway-key pacman package/database signatures. The prior published tree remains intact on failure. Verified the committed bootstrap database/signature against pinned repository key 56203B86A110695AE7F310934AF3323D678EB5E2. Tests independently recheck that evidence, strict file/identity/policy validation, provenance recording, YAML/shell blocks, Python compilation and diff checks. New build entries require reviewed protected-main policy advancement, including the separate breadlock producer. No production registry tampering, live configs, desktop or keys were used.
Author
Owner

Independent review of PR #78 at 7716e35247c8bcc910cec5bb39f50006ac0c6958

Acceptance interpretation: an archive replaced in the Forgejo registry under the same package name/version must be rejected before the signer creates a signature or changes the published repository. scripts/package-provenance.py:34-60,89-106 requires an exact protected name, version, architecture, filename and SHA-256 set. scripts/ci-publish-signed-repo.sh:88-98,140-160 checks those bytes before signing and requests approved filenames directly. scripts/test-package-provenance.py:74-94 exercises a same-version payload replacement against a loopback registry and checks the prior tree and lack of signatures. The bootstrap database and detached signature are checked against the pinned repository identity in the same test. These are behavioral checks, not just structure assertions.

The code is ready for the BOS release maintainer's review. PR #78 is mergeable but stacked on BOS-39 PR #75, so #75 must land first and #78 should then target main. Forgejo reports no CI statuses on this head. The offline signing key migration and protected policy approval remain activation gates; this review does not imply those have happened. No merge attempted.

## Independent review of PR #78 at `7716e35247c8bcc910cec5bb39f50006ac0c6958` Acceptance interpretation: an archive replaced in the Forgejo registry under the same package name/version must be rejected before the signer creates a signature or changes the published repository. `scripts/package-provenance.py:34-60,89-106` requires an exact protected name, version, architecture, filename and SHA-256 set. `scripts/ci-publish-signed-repo.sh:88-98,140-160` checks those bytes before signing and requests approved filenames directly. `scripts/test-package-provenance.py:74-94` exercises a same-version payload replacement against a loopback registry and checks the prior tree and lack of signatures. The bootstrap database and detached signature are checked against the pinned repository identity in the same test. These are behavioral checks, not just structure assertions. The code is ready for the BOS release maintainer's review. PR #78 is mergeable but stacked on BOS-39 PR #75, so #75 must land first and #78 should then target main. Forgejo reports no CI statuses on this head. The offline signing key migration and protected policy approval remain activation gates; this review does not imply those have happened. No merge attempted.
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
Breadway/bos#40
No description provided.