Skip to content

ci: harden release pipeline against supply-chain#95

Merged
KyleJune merged 1 commit into
mainfrom
ci/harden-supply-chain-36
Jun 14, 2026
Merged

ci: harden release pipeline against supply-chain#95
KyleJune merged 1 commit into
mainfrom
ci/harden-supply-chain-36

Conversation

@KyleJune

Copy link
Copy Markdown
Member

Summary

Hardens the release pipeline against the npm / GitHub Actions supply-chain attack
class (udibo/udibo#36). The publish job is the only one with id-token: write, so
the goal is that the only code running there is pinned and integrity-checked.

Changes

  • Run semantic-release through Deno, not npx.
    deno run -A npm:semantic-release resolves semantic-release and all plugins
    from deno.lock under minimumDependencyAge, with npm lifecycle scripts
    disabled by default. npx fetched them fresh and unpinned on every release
    while id-token: write was active — the exact exposure the postmortem
    describes. actions/setup-node is removed (no more node/npm in CI).
  • Versions pinned in deno.json: semantic-release ^24 (v25's engines
    >=24.10 reject Deno's emulated Node 24.2.0); @sebbo2002/semantic-release-jsr
    held at 3.2.1 (4.0.0 is EPLUGINSCONF-broken); plugins pinned via the lockfile.
  • Pin every GitHub Action to a commit SHA, including
    amannn/action-semantic-pull-request which runs in the privileged
    pull_request_target title check.
  • Top-level permissions: contents: read so the non-release jobs drop from
    the default write token; the release job keeps its explicit elevated block.
  • cache: false on the publish job's setup-deno — the OIDC job restores no
    shared cache.
  • deno ci instead of deno install; documented why pr-title.yml's
    pull_request_target is safe (no checkout, no cache, read-only).

Trade-off

Release tooling now lives in the workspace deno.json, so deno ci / deno install pulls it for all contributors (cached in CI). Kept simple and
Deno-native per the issue; a dev/prod split can follow if install weight matters.

Testing

Verified locally on Deno 2.8.2: deno run -A npm:semantic-release --dry-run loads
all six plugins and correctly no-ops off main; deno ci and deno task check
pass.

Run semantic-release through Deno instead of npx so the publish tooling
resolves from deno.lock under minimumDependencyAge with npm lifecycle
scripts disabled — the only code running while id-token:write is active is
now pinned and integrity-checked. Drop actions/setup-node, pin every action
to a commit SHA, add a top-level contents:read token, disable the shared
cache on the publish job, switch installs to `deno ci`, and set
minimumDependencyAge. semantic-release is held at ^24 because v25's engines
reject Deno's emulated Node. Addresses udibo/udibo#36.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KyleJune KyleJune merged commit 453d6cb into main Jun 14, 2026
10 checks passed
@KyleJune KyleJune deleted the ci/harden-supply-chain-36 branch June 14, 2026 15:00
KyleJune added a commit that referenced this pull request Jun 14, 2026
@sebbo2002/semantic-release-jsr shell-spawns its own downloaded `deno
publish`, and that spawn's args get mangled by Deno's child_process
Node-compat layer — so after #95 moved semantic-release onto `deno run`,
the publish failed with "unrecognized subcommand". Restore the Node host
(actions/setup-node + npx), keeping the supply-chain hardening by pinning
every npx package to an exact version (jsr plugin stays 3.2.1). The
SHA-pinned actions, contents:read token, and minimumDependencyAge from #95
are unchanged; the semantic-release imports #95 added to deno.json are
removed since npx fetches the tooling again.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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