Skip to content

fix(release): harden the release pipeline before signing secrets land - #1

Merged
adamdotdevin merged 3 commits into
devfrom
release-hardening
Nov 10, 2026
Merged

adamdotdevin merged 3 commits into
devfrom
release-hardening

Conversation

@adamdotdevin

@adamdotdevin adamdotdevin commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Fixes the systems review of 144dbea before any signing secrets are added to the signing environment.

Findings

  1. No executable caches in the release workflow. dev is the default branch, so a production run could restore a cache a dev run wrote. setup-bun gets no-cache: true, the Azure signing action cache-dependencies: false, and both setup-node steps package-manager-cache: false. No other step restores a cache.
  2. Signer archive handling. New lib.sh helpers members, unpack and repack. unpack lists a tarball first and requires exactly the target's regular files, each once (no links, directories, duplicates or extras), then extracts by those names. repack packs the fixed names plus the signature, never a list read back from the unpacked tree, which removes the tar -T newline injection. Used by sign-linux.sh, sign-macos.sh, windows-binaries.sh and script/e2e/signed.sh.
  3. Pinned release-service client. .github/release/client.sh.sha256 pins the client.sh the service serves today (f19e3852…526e). release_client checks it before running, in publish-release-service.sh and in finish. RELEASING.md says how to move the pin.
  4. Tag reservation. A stable-only reserve-tag job creates refs/tags/v<version> at the run's SHA through the API before anything publishes. If the tag exists, it peels annotated tags and fails unless the tag is at that SHA. finish checks the tag again, then runs gh release create --verify-tag, or re-uploads with --clobber on a rerun after the release exists. A rerun at the same SHA finds its own tag and goes on.
  5. Channel switch rollback. install() drops cli.prev when setup moves the install to another channel, and restore/previousCommand refuse a kept build from another channel before self-testing it.
  6. Clean subprocess environment. New src/update/environment.ts. tar, codesign, PowerShell and a new build's self-test run with a fixed environment. The detached updater keeps only what the CLI reads (data directories, OPENCODE_*, proxies, CA files, agent config locations including HERMES_DATA_DIR_SUFFIX) and drops the rest (TAR_OPTIONS, BUN_OPTIONS, NODE_OPTIONS, LD_PRELOAD, …). On Windows, SystemRoot is trusted only as <drive>:\Windows; anything else falls back to C:\Windows.
  7. Promotion PR lookup. Only a PR whose head is this repository's own dev counts (isCrossRepository == false and headRepository.nameWithOwner), so a fork's dev can't suppress it.
  8. Docs and comments.
    • The Azure subject is now repo:anomalyco@66570915/opencode-console-cli@1452060248:environment:signing; the repository uses immutable OIDC subjects.
    • The .github push ruleset is described as private-only.
    • RELEASING.md covers masking fetched secrets (::add-mask::) when moving to AWS Secrets Manager, and that writers can rerun failed jobs of a maintainer-dispatched run.
    • The docs and installer hints no longer point at /console/install, which isn't served yet; they use the raw.githubusercontent.com production URLs until Console's proxy ships.

Settings changed (already applied)

  • Actions token: default GITHUB_TOKEN is read-only. "Allow GitHub Actions to create and approve pull requests" stays on for the promotion workflow. Every workflow sets its own permissions.
  • Production gate: new ruleset production: maintainers merge adds an update rule on refs/heads/production. Only the Repository admin and Maintain roles can bypass it, so only a maintainer can merge the promotion PR. It's a separate ruleset, so the bypass doesn't also exempt maintainers from production's PR and Checks rules. A write-role user is blocked; verified by reading the effective rules, since it can't be tested without a write-role account.
  • The dev ruleset is unchanged.

Red/green (Linux VM, GNU tar 1.35)

Test Goes red against
Archive tests: directory as package/LICENSE, newline in a member name, extra/duplicate/link members, Windows signer the original lib.sh and signers
A client.sh that isn't the pinned one is refused before it runs lib.sh without the hash check
reserve-tag: tag at another commit; annotated tag peeled without the SHA comparison (2 fail); without peeling (1 fail)
Never rolls back to, or self-tests, a build kept from another channel the original src/install.ts; the guard alone removed
Setup moving the install to another channel drops cli.prev the original src/install.ts
The detached updater doesn't inherit TAR_OPTIONS or BUN_OPTIONS the original update modules
A self-update installs despite TAR_OPTIONS=--exclude=package/bin/opencode-console the original self-update.ts (Linux; macOS's bsdtar ignores TAR_OPTIONS)
The detached refresh keeps HERMES_DATA_DIR_SUFFIX and finds the same Hermes home KEPT without it

Given a crafted tarball (a directory at package/LICENSE holding a and a\n/tmp/…/runner-secret), the original sign-linux.sh exited 0 and its signed tarball carried the runner's file. The new one refuses before extracting.

Also passing on the VM: bun run check, bun run test (846 pass, 2 macOS-only skips), script/e2e/run.sh 36/36, script/e2e/signed.sh 30/30.

Not tested

  • Windows: the PowerShell signature check with the fixed environment, and tar.exe resolved from the validated SystemRoot. Nothing here ran on Windows.
  • reserve-tag outside its stand-in: it's tested against a stand-in gh api, not GitHub's API. check runs skip it because it needs contents: write and check can run from any branch. Its first real run is the first stable release.

@adamdotdevin
adamdotdevin requested a review from thdxr as a code owner November 10, 2026 03:38
@adamdotdevin
adamdotdevin merged commit 7b7ccfe into dev Nov 10, 2026
11 checks passed
@adamdotdevin
adamdotdevin deleted the release-hardening branch November 10, 2026 12:20
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.

1 participant