Skip to content

fix(dashmate)!: give Debian packages versions apt can order - #4282

Open
shumkov wants to merge 2 commits into
v4.2-devfrom
feat/dashmate/deb-version-ordering
Open

fix(dashmate)!: give Debian packages versions apt can order#4282
shumkov wants to merge 2 commits into
v4.2-devfrom
feat/dashmate/deb-version-ordering

Conversation

@shumkov

@shumkov shumkov commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

apt cannot order dashmate's Debian packages, and nothing we publish can be verified.

oclif builds the deb version as <upstream>.<git sha>-1. That discards the semver prerelease tag and makes the git sha an ordering component — and under dpkg's comparison digits sort below letters, so ordering between builds is effectively random. Verified against the real published 4.1.0 series:

transition deb versions apt verdict
beta.2 → rc.1 4.1.0.08152ea51e-1 vs 4.1.0.ae554fdd83-1 refused as a downgrade
rc.1 → rc.2 4.1.0.3de436123d-1 vs 4.1.0.08152ea51e-1 refused as a downgrade
rc.2 → rc.3 4.1.0.61be67f7bf-1 vs 4.1.0.3de436123d-1 upgrade
rc.3 → 4.1.0 4.1.0.bfc80249b9-1 vs 4.1.0.61be67f7bf-1 upgrade

Two of four real transitions are read as downgrades. Worse, a same-version security rebuild never ships: with 4.1.0.bfc80249b9-1 installed, a hotfix built from a later commit compares lower, so apt reports the package as already newest and the operator believes they are patched.

Separately, nothing in the release is verifiable — no checksums, no signatures beyond the macOS notarisation, and the apt metadata oclif generates is uploaded unsigned and unserved.

What was done?

Monotonic Debian versions. 4.1.0~rc.3-1 sorts below 4.1.0-1; rebuilds bump the Debian revision; the git sha moves into the package description where it cannot affect ordering. Filenames deliberately diverge from the control version, because GitHub rewrites ~ and : in release asset names — the control field keeps them, the filename drops them, and dpkg-name strips epochs from filenames for the same reason.

A release-blocking ordering gate. It reads every version from the published package via dpkg-deb -f rather than deriving it from a tag, and the packaging job then asserts the built package carries the version that was gated — so the check binds to the bytes that ship.

Verifiable releases. Every published asset is hashed into a deterministic SHA256SUMS, in a job that checks out nothing and installs nothing, so no dependency lifecycle script can run beside the signing key that job will later hold. Each packaging leg records the hashes it produced and the checksum job refuses any asset it did not build. npm packages publish with provenance where the registry accepts it. Third-party actions are pinned to commit SHAs, Binaryen is checksummed, and the jobs holding credentials name environments so tag and reviewer policies attach to them.

How Has This Been Tested?

Verified against real dpkg (installed locally) rather than a port of its algorithm: every transition in the 4.1.0 series now sorts strictly upward, and dpkg ordering agrees with semver precedence across all 15 prerelease forms the mapping can produce. The version mapping is injection-proof (shell metacharacters, command substitution, embedded newlines all rejected) and the round-trip preserves symlinks, hardlinks, conffiles, maintainer scripts and exec bits.

Not executed: apt-ftparchive index regeneration needs a Linux host, and no real release run has exercised the workflow changes. Both are recorded as gates rather than assumed.

Breaking Changes

Debian package filenames and versions change shape. A rebuild of an already-published version needs DASHMATE_DEB_REVISION; re-releasing a version published under the old scheme needs DASHMATE_DEB_EPOCH, because 4.1.0-1 sorts below the old 4.1.0.bfc80249b9-1.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Debian packages now use validated, upgrade-safe versioning, including prerelease support.
    • Release builds generate and verify package checksums before publication.
    • Package publishing includes provenance verification where supported.
    • Installation instructions automatically retrieve the latest package for the system architecture.
  • Bug Fixes

    • Prevented releases from being treated as Debian package downgrades.
    • Added validation to detect mismatched package versions and stale checksums.

shumkov and others added 2 commits August 4, 2026 19:22
oclif builds the Debian version as <upstream>.<git sha>-1, which discards the
semver prerelease tag and makes the git sha an ordering component. Under dpkg's
comparison digits sort below letters, so ordering between builds is effectively
random: of the four real transitions in the 4.1.0 series, apt reads two as
downgrades. A same-version security rebuild is worse still, since it compares
lower and apt reports the package as already newest while the operator believes
they are patched.

Versions are now Debian-idiomatic and monotonic: 4.1.0~rc.3-1 sorts below
4.1.0-1, rebuilds bump the Debian revision, and the sha moves into the package
description where it cannot affect ordering. Verified against real dpkg, which
also confirms this ordering agrees with semver precedence for every prerelease
form the mapping can produce.

Filenames deliberately diverge from the control version: GitHub rewrites the
tilde and colon in release asset names, so both are stripped from the filename
while the control field keeps them. dpkg-name strips epochs from filenames for
the same reason.

BREAKING CHANGE: Debian package filenames and versions change shape. A rebuild
of an already published version needs DASHMATE_DEB_REVISION, and re-releasing a
version published under the old scheme needs DASHMATE_DEB_EPOCH.

Test would have caught this in CI: 4 of the new specs fail before the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing published today can be verified: there are no checksums, no signatures
beyond the macOS notarisation, and the apt metadata oclif generates is uploaded
unsigned and unserved. This adds the pieces that do not depend on where the
repository will eventually be hosted.

Every published asset is now hashed into a deterministic SHA256SUMS, in a job
that checks out nothing and installs nothing so no dependency lifecycle script
can run beside the signing key that job will later hold. Each packaging leg
records the hashes it produced and the checksum job refuses any asset it did not
build, so the file attests what was built rather than whatever is attached.

npm packages publish with provenance where the registry will accept it, which
required correcting dashmate's own repository field; the rest publish as before
with a warning naming the manifest to fix, so a metadata gap cannot fail a
release mid-loop.

The Debian version gate refuses a release apt would read as a downgrade. It
reads every version from the published package rather than deriving it from a
tag, and the packaging job asserts the built package carries the version that
was gated, so the check binds to the bytes that ship.

Third-party actions are pinned to commit SHAs, Binaryen is checksummed, and the
jobs holding credentials name environments so tag and reviewer policies can be
attached to them.

Note the npm publish job cannot avoid running install scripts, because packing
runs prepack and prepublishOnly hooks. That exposure is documented in the
workflow rather than claimed to be solved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Dashmate release packaging

Layer / File(s) Summary
Debian version conversion and validation
scripts/deb_version.js, scripts/check_deb_version.sh, packages/dashmate/test/unit/packaging/debVersion.spec.js
Semver conversion, Debian comparison, filename conversion, CLI handling, and comprehensive ordering tests are added.
Debian package rewriting
scripts/pack_dashmate.sh
Generated Debian packages receive converted versions, build metadata, rebuilt control files, and regenerated repository indexes.
Build and npm publishing workflow
.github/workflows/release.yml, packages/dashmate/package.json
NPM build and publish jobs are separated. Provenance is applied when repository metadata matches. Actions and Binaryen downloads use pinned or verified sources.
Debian release gate and package verification
.github/workflows/release.yml
The workflow compares the candidate Debian version with a predecessor release and checks that built packages contain the validated version.
Published package checksum verification
.github/workflows/release.yml
Built package checksums are recorded and compared with published release assets before SHA256SUMS is uploaded.
Dynamic Debian installation instructions
packages/dashmate/docs/installation.md
Installation retrieves the latest package for the host architecture through the GitHub API and installs it with a wildcard filename.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseWorkflow
  participant VersionGate
  participant PackageBuilder
  participant ReleaseAssets
  participant ChecksumJob
  VersionGate->>ReleaseWorkflow: validate candidate Debian version
  ReleaseWorkflow->>PackageBuilder: build packages with validated version
  PackageBuilder->>ReleaseWorkflow: upload packages and built checksums
  ReleaseWorkflow->>ReleaseAssets: publish package assets
  ChecksumJob->>ReleaseAssets: download published assets
  ChecksumJob->>PackageBuilder: download built checksum artifacts
  ChecksumJob->>ReleaseAssets: upload verified SHA256SUMS
Loading

Suggested reviewers: quantumexplorer

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: correcting Debian package versions so apt can order them correctly.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashmate/deb-version-ordering

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

⛔ Blockers found — Sonnet deferred (commit 773bc64)
Canonical validated blockers: 3

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
scripts/deb_version.js (1)

41-41: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Reject epochs with leading zeros.

EPOCH_REGEX accepts 01, so DASHMATE_DEB_EPOCH=01 yields 01:4.1.0-1. dpkg parses the epoch numerically, so 01: and 1: compare equal while the control field text differs from the value the release gate echoes and the packaging check compares as a string. The Check the built deb carries the validated version step in .github/workflows/release.yml compares version strings exactly, so any normalization difference becomes a hard failure. Restrict the epoch the same way the numeric identifier is restricted.

♻️ Proposed change
-const EPOCH_REGEX = /^\d+$/;
+const EPOCH_REGEX = /^(?:0|[1-9]\d*)$/;

Also applies to: 66-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/deb_version.js` at line 41, Update EPOCH_REGEX in
scripts/deb_version.js to reject leading zeros while still accepting the valid
zero epoch and nonzero numeric epochs, matching the existing numeric-identifier
validation behavior. Ensure epoch values such as 01 are rejected before version
construction and validation output.
scripts/check_deb_version.sh (1)

44-59: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Handle a malformed version explicitly.

dpkg --compare-versions exits 2 when either argument is not a parsable Debian version. Both if tests then evaluate false, and the script reports "$NEW_VERSION sorts below $PREVIOUS_VERSION" and exits 1. That message is wrong for a parse error, and the operator advice about DASHMATE_DEB_EPOCH does not apply. Validate both arguments first.

♻️ Proposed change
+for version in "$NEW_VERSION" "$PREVIOUS_VERSION"
+do
+  if ! dpkg --validate-version "$version" > /dev/null 2>&1
+  then
+    echo "check_deb_version.sh: \"$version\" is not a valid Debian version." >&2
+    exit 2
+  fi
+done
+
 if dpkg --compare-versions "$NEW_VERSION" gt "$PREVIOUS_VERSION"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_deb_version.sh` around lines 44 - 59, Validate both NEW_VERSION
and PREVIOUS_VERSION with dpkg --compare-versions before the
greater-than/equality checks in the version comparison flow. Detect its
parse-error status explicitly, report that the version input is malformed, and
exit nonzero without using the downgrade message or DASHMATE_DEB_EPOCH guidance;
preserve the existing ordering behavior for valid versions.
.github/workflows/release.yml (1)

699-720: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

The published-asset check is one-directional.

The loop proves that every published hash came from a packaging job. It does not prove that every built package reached the release. If an asset is deleted or an upload silently fails, SHA256SUMS simply omits it and the step reports success. The comment at Line 678 states that apt publication refuses any package whose hash is not in the signed file, so a missing entry becomes a silent omission of a package rather than a detected fault.

Add the reverse check: every hash recorded by a packaging job must appear in SHA256SUMS.

♻️ Proposed addition
           if [ "${unmatched}" -ne 0 ]; then
             echo "::error::Published assets do not match the built packages; refusing to publish checksums"
             exit 1
           fi
+
+          cut -d' ' -f1 assets/SHA256SUMS | LC_ALL=C sort -u > "${RUNNER_TEMP}/published-hashes"
+          missing="$(comm -23 "${RUNNER_TEMP}/built-hashes" "${RUNNER_TEMP}/published-hashes")"
+          if [ -n "${missing}" ]; then
+            echo "::error::These built package hashes are not published: ${missing}"
+            exit 1
+          fi
           echo "All published assets match packages built in this run"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 699 - 720, Extend the
verification step after the existing published-asset loop to also validate
completeness: iterate over the unique hashes in "${RUNNER_TEMP}/built-hashes"
and require each to appear in the first field of assets/SHA256SUMS. Report
missing published assets, set unmatched, and preserve the existing failure path
that refuses publication when unmatched is nonzero.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 45-46: Update all three release workflow checkout steps using
actions/checkout@v4 to set persist-credentials to false, unless the
corresponding job has a later step requiring authenticated Git access; preserve
authentication only for those jobs that need it.

In `@packages/dashmate/docs/installation.md`:
- Around line 37-38: The shell glob pattern ./dashmate_*.deb expands before apt
runs and can match multiple files (older packages or wrong architectures),
causing installation failures. Replace the glob pattern in the sudo apt install
command with an explicit variable or filename that captures the exact basename
from the preceding curl -o download operation, ensuring only the intended
downloaded package is passed to apt.

In `@scripts/pack_dashmate.sh`:
- Around line 160-164: Update the DASHMATE_DEB_KEY check in the signing block to
use a default-safe parameter expansion, so the unset variable is treated as
empty under set -u and unsigned local builds continue without signing.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 699-720: Extend the verification step after the existing
published-asset loop to also validate completeness: iterate over the unique
hashes in "${RUNNER_TEMP}/built-hashes" and require each to appear in the first
field of assets/SHA256SUMS. Report missing published assets, set unmatched, and
preserve the existing failure path that refuses publication when unmatched is
nonzero.

In `@scripts/check_deb_version.sh`:
- Around line 44-59: Validate both NEW_VERSION and PREVIOUS_VERSION with dpkg
--compare-versions before the greater-than/equality checks in the version
comparison flow. Detect its parse-error status explicitly, report that the
version input is malformed, and exit nonzero without using the downgrade message
or DASHMATE_DEB_EPOCH guidance; preserve the existing ordering behavior for
valid versions.

In `@scripts/deb_version.js`:
- Line 41: Update EPOCH_REGEX in scripts/deb_version.js to reject leading zeros
while still accepting the valid zero epoch and nonzero numeric epochs, matching
the existing numeric-identifier validation behavior. Ensure epoch values such as
01 are rejected before version construction and validation output.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a1c5837-d2d3-4e64-8cba-f135f4e15942

📥 Commits

Reviewing files that changed from the base of the PR and between 97904ed and 773bc64.

📒 Files selected for processing (7)
  • .github/workflows/release.yml
  • packages/dashmate/docs/installation.md
  • packages/dashmate/package.json
  • packages/dashmate/test/unit/packaging/debVersion.spec.js
  • scripts/check_deb_version.sh
  • scripts/deb_version.js
  • scripts/pack_dashmate.sh

Comment on lines 45 to 46
- name: Check out repo
uses: actions/checkout@v4

@coderabbitai coderabbitai Bot Aug 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow relevant checkout/token/action references =="
rg -n "actions/checkout|persist-credentials|contents|permissions:|GITHUB_TOKEN|yarn install|cargo|wasm|uses:" .github/workflows/release.yml

echo
echo "== checkout occurrences with surrounding lines =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/release.yml')
lines=p.read_text().splitlines()
for target in [45, 177, 346]:
    print(f"\n--- around line {target} ---")
    for i in range(max(1,target-8), min(len(lines), target+12)+1):
        print(f"{i:4}: {lines[i-1]}")
PY

Repository: dashpay/platform

Length of output: 146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow size =="
wc -l .github/workflows/release.yml

echo
echo "== quick targeted matches =="
grep -nE "actions/checkout|persist-credentials|contents|permissions:|GITHUB_TOKEN|yarn install|cargo|wasm|uses:" .github/workflows/release.yml || true

echo
echo "== checkout occurrences with surrounding lines =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/release.yml')
lines=p.read_text().splitlines()
for target in [45, 177, 346]:
    print(f"\n--- around line {target} ---")
    start=max(1,target-6)
    end=min(len(lines), target+10)
    for i in range(start,end+1):
        print(f"{i:4}: {lines[i-1]}")
PY

Repository: dashpay/platform

Length of output: 146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow size =="
wc -l .github/workflows/release.yml

echo
echo "== checkout occurrences with surrounding lines =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/release.yml')
lines=p.read_text().splitlines()
for target in [45, 177, 346]:
    print(f"\n--- around line {target} ---")
    start=max(1,target-6)
    end=min(len(lines), target+10)
    for i in range(start,end+1):
        print(f"{i:4}: {lines[i-1]}")
PY

Repository: dashpay/platform

Length of output: 2394


🌐 Web query:

actions/checkout persist-credentials default true GitHub token local git config

💡 Result:

In the GitHub actions/checkout action, the persist-credentials input defaults to true [1][2][3]. When enabled, this setting configures the authentication token (or SSH key) into the local git configuration [1][4]. This allows subsequent git commands executed within your workflow steps to automatically use these credentials without requiring manual authentication [1][5]. Important technical details regarding this mechanism include: 1. Security and Lifecycle: The token is automatically removed during the post-job cleanup process [1][5]. 2. Storage Location: While traditionally stored directly in the local git config, version 6.0.0 and later updated this behavior to store the credentials under $RUNNER_TEMP to improve security, such as preventing leaks through artifact publishing [6]. 3. Opt-out: If you do not want your credentials persisted to the git configuration, you can explicitly set persist-credentials to false in your workflow file [1][5]. Example configuration to disable persistence: - uses: actions/checkout@v4 with: persist-credentials: false

Citations:


Sensitive Data Exposure (CWE-522): Insufficiently Protected Credentials

Disable credential persistence on checkout steps that do not need git authentication.

actions/checkout@v4 persists the GitHub token by default, so later workflow steps can use it until checkout cleanup. Add persist-credentials: false to the three release workflow checkouts unless a later step in that job needs authenticated Git access.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 45-46: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 45 - 46, Update all three release
workflow checkout steps using actions/checkout@v4 to set persist-credentials to
false, unless the corresponding job has a later step requiring authenticated Git
access; preserve authentication only for those jobs that need it.

Source: Linters/SAST tools

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three cited checkouts persist only a contents: read token in this public repository, no .git data is uploaded, and no later step needs authenticated Git. The separate write-scoped packaging checkout predates this PR and is not covered by the comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thepastaclaw, the contents: read scope and the absence of .git uploads reduce the impact. The three jobs also do not need authenticated Git after checkout.

However, actions/checkout@v4 still persists an unnecessary credential for the job lifetime. Setting persist-credentials: false remains a valid least-privilege hardening change. I would keep this as a minor finding.


✏️ Learnings added
Learnt from: thepastaclaw
Repo: dashpay/platform PR: 4282
File: .github/workflows/release.yml:45-46
Timestamp: 2026-08-04T17:05:22.168Z
Learning: In `.github/workflows/release.yml`, the `release-npm`, `publish-npm`, and `check-dashmate-deb-version` jobs have `contents: read` permissions, do not upload `.git` data, and do not require authenticated Git operations after their `actions/checkoutv4` steps.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread packages/dashmate/docs/installation.md
Comment thread scripts/pack_dashmate.sh

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preliminary review — Codex only

The workflow improves privilege separation and release verification, but three blocking gaps remain. The checksum gate does not bind hashes to filenames or require all built artifacts to be present, the Debian baseline can be selected using release creation order instead of publication order, and mutable actions inside local composites undermine the PR's action-pinning guarantee.

Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 3 blocking

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `.github/workflows/release.yml`:
- [BLOCKING] .github/workflows/release.yml:699-720: Bind published checksums to filenames and require completeness
  The verification reduces the build records to a set of hashes and only checks that each published hash appears in that set. Removing a built asset therefore still passes, and bytes built for one architecture can be published under another architecture's filename because that hash was produced by some matrix leg. This can produce and later sign an incomplete or mislabeled SHA256SUMS. Normalize the recorded paths to release basenames and compare the complete `(hash, filename)` sets in both directions.
- [BLOCKING] .github/workflows/release.yml:394-403: Order Debian baselines by publication time
  The candidate list is intended to represent releases in the order operators could install them, but it sorts by `created_at`. A release can be created as a draft and published after another release created later. On a rerun, an older-created current release can then lose to its predecessor even though the current release already has a Debian asset, allowing a same-version rebuild to pass without incrementing the Debian revision. Sort by `published_at` so the gate compares against the most recently exposed package.
- [BLOCKING] .github/workflows/release.yml:58-72: Pin third-party actions used by local composites
  The PR explicitly claims that third-party actions are pinned, but the local Rust and sccache composites invoked after DockerHub login still resolve `dtolnay/rust-toolchain@master` and `mozilla-actions/sccache-action@v0.0.6`. Those mutable external actions execute in the credential-bearing build job and can modify the workspace before `yarn build`; the resulting artifact is then packaged, hashed, and published by the new release path. Pin the external `uses` references in `.github/actions/rust/action.yaml:42` and `.github/actions/sccache/action.yaml:80` to full commit SHAs.

Comment on lines +699 to +720
- name: Verify published assets against the built packages
run: |
set -euo pipefail
cat built-checksums/*/built.sha256 > "${RUNNER_TEMP}/built-all.sha256"
if [ ! -s "${RUNNER_TEMP}/built-all.sha256" ]; then
echo "::error::No built package checksums were recorded"
exit 1
fi
cut -d' ' -f1 "${RUNNER_TEMP}/built-all.sha256" | LC_ALL=C sort -u > "${RUNNER_TEMP}/built-hashes"

unmatched=0
while read -r hash name; do
if ! grep -qxF "${hash}" "${RUNNER_TEMP}/built-hashes"; then
echo "::error::${name} (${hash}) was not produced by any packaging job"
unmatched=1
fi
done < assets/SHA256SUMS
if [ "${unmatched}" -ne 0 ]; then
echo "::error::Published assets do not match the built packages; refusing to publish checksums"
exit 1
fi
echo "All published assets match packages built in this run"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Bind published checksums to filenames and require completeness

The verification reduces the build records to a set of hashes and only checks that each published hash appears in that set. Removing a built asset therefore still passes, and bytes built for one architecture can be published under another architecture's filename because that hash was produced by some matrix leg. This can produce and later sign an incomplete or mislabeled SHA256SUMS. Normalize the recorded paths to release basenames and compare the complete (hash, filename) sets in both directions.

Suggested change
- name: Verify published assets against the built packages
run: |
set -euo pipefail
cat built-checksums/*/built.sha256 > "${RUNNER_TEMP}/built-all.sha256"
if [ ! -s "${RUNNER_TEMP}/built-all.sha256" ]; then
echo "::error::No built package checksums were recorded"
exit 1
fi
cut -d' ' -f1 "${RUNNER_TEMP}/built-all.sha256" | LC_ALL=C sort -u > "${RUNNER_TEMP}/built-hashes"
unmatched=0
while read -r hash name; do
if ! grep -qxF "${hash}" "${RUNNER_TEMP}/built-hashes"; then
echo "::error::${name} (${hash}) was not produced by any packaging job"
unmatched=1
fi
done < assets/SHA256SUMS
if [ "${unmatched}" -ne 0 ]; then
echo "::error::Published assets do not match the built packages; refusing to publish checksums"
exit 1
fi
echo "All published assets match packages built in this run"
cat built-checksums/*/built.sha256 > "${RUNNER_TEMP}/built-all.sha256"
if [ ! -s "${RUNNER_TEMP}/built-all.sha256" ]; then
echo "::error::No built package checksums were recorded"
exit 1
fi
while read -r hash name; do
printf '%s %s\n' "${hash}" "${name##*/}"
done < "${RUNNER_TEMP}/built-all.sha256" \
| LC_ALL=C sort > "${RUNNER_TEMP}/built-assets.sha256"
LC_ALL=C sort assets/SHA256SUMS > "${RUNNER_TEMP}/published-assets.sha256"
if ! diff -u "${RUNNER_TEMP}/built-assets.sha256" "${RUNNER_TEMP}/published-assets.sha256"; then
echo "::error::Published assets do not exactly match the packages built in this run"
exit 1
fi
echo "All published assets match packages built in this run"

source: ['codex']

Comment on lines +394 to +403
gh api --paginate --slurp "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
| jq -r '
add
| sort_by(.created_at)
| reverse
| .[]
| select(.draft == false)
| { tag: .tag_name, asset: ([.assets[].name | select(endswith("_amd64.deb"))] | first) }
| select(.asset != null)
| "\(.tag)\t\(.asset)"' > "${candidates}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Order Debian baselines by publication time

The candidate list is intended to represent releases in the order operators could install them, but it sorts by created_at. A release can be created as a draft and published after another release created later. On a rerun, an older-created current release can then lose to its predecessor even though the current release already has a Debian asset, allowing a same-version rebuild to pass without incrementing the Debian revision. Sort by published_at so the gate compares against the most recently exposed package.

Suggested change
gh api --paginate --slurp "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
| jq -r '
add
| sort_by(.created_at)
| reverse
| .[]
| select(.draft == false)
| { tag: .tag_name, asset: ([.assets[].name | select(endswith("_amd64.deb"))] | first) }
| select(.asset != null)
| "\(.tag)\t\(.asset)"' > "${candidates}"
| sort_by(.published_at)

source: ['codex']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants