harden(publish): release-env gate + tag-version assert + dist-tag fix#36
Merged
Conversation
Supply-chain hardening for the publish pipeline (v1.6.4 cross-audit dimension d + Overwatch card). Token blast-radius is already closed (Sean made NPM_TOKEN granular, 2-package scope); this PR is the WHO-can-trigger + what-can-ship half. WORKFLOW-only + a runbook note -- no live admin edits. publish.yml: - gate the publish job on the protected `release` environment (required reviewer): a v* tag, regardless of who pushes it, now pauses for human approval before any npm publish runs. Overwatch/Sean configure the env protection + the tag-creation ruleset (only the release actor may create v*) outside the workflow. - assert the pushed tag matches installer/package.json AND mcp-server/ package.json version before publishing. Without it, `git tag v9.9.9` on a 1.6.5 commit would publish 1.6.5 under a v9.9.9 Release. check-all's 9-surface version-lockstep gate (rebased in from #35) proves all in-repo surfaces agree, so tag == installer+mcp + lockstep == tag == every published + manifest version. - fix the dist-tag rule: the old check matched ONLY `-rc.`, so a `-beta`/`-alpha` tag silently shipped to `latest`. Now a strict anchored regex validates the tag shape FIRST (adversarial review: the old `case` globs would have let `v1.2.3.4`/`v1.2.3extra` fall through to `latest`), then stable -> latest, ANY prerelease -> next, malformed -> refused. - injection-safe: tag values flow via env (TAG_REF), never inlined into run: scripts (verified an injected `; rm -rf` tag is neutralized). docs/CI-PUBLISH.md: 'current pipeline' callout overriding the stale legacy 'no NPM_TOKEN / nothing to rotate' line -- documents the granular token + its ~2026-10-08 (90-day) ROTATION deadline, the release-env gate, the tag<->version assert, and the dist-tag rule. Plus a settings-audit block (gh api one-liners) so an operator can VERIFY the release-env required- reviewer + tag-creation ruleset are actually applied -- the workflow can't enforce them and an unconfigured environment auto-creates UNPROTECTED. Verified: publish.yml valid YAML, environment key present on publish job, banned-char clean; dist-tag + assert logic unit-tested across stable/rc/ beta/alpha/malformed/injection tags + matching/mismatching versions. Out of scope (per Overwatch): token/OIDC migration (granular suffices), account classic-token cleanup (Sean owns), the tag-creation ruleset + release-env protection SETTINGS (admin, not workflow). Overwatch reviews this as release-infra.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Publish-hardening: release-env gate + tag↔version assert + dist-tag fix
Overwatch review requested (release/publish-security infra — do not auto-merge).
Workflow + docs only. No code, no admin/settings edits.
What this delivers (per the publish-hardening card)
publishjob now runs underenvironment: release.A
v*tag, no matter who pushes it, pauses for required-reviewer approval before anynpm publish. (The environment protection itself is admin-configured in reposettings, outside this workflow — see the audit block below.)
equals both
installer/package.jsonandmcp-server/package.jsonversions.Combined with the 9-surface version-lockstep gate now in
check-all.sh(landed infix(release): lockstep all 9 version surfaces + enforce in CI #35, which this branch is rebased onto), a green release means tag == every one of
the 9 in-repo release surfaces, including codex.
(
^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$), then stable→latest, ANYprerelease→
next, malformed→refused. The oldcaseglobs would have routedv1.2.3.4/v1.2.3extratolatest; the old-rc.-only match shipped-beta/-alphatolatest. Both closed.NPM_TOKEN~2026-10-08(90-day) rotation deadline, plus a
gh apisettings-audit block to verify therelease-env required reviewer (≥1) and the
refs/tags/v*creation ruleset areactually applied (the workflow cannot self-enforce either).
Verification
check-all.shgreen on the rebased tree (banned-char, 9-surface lockstep, fullmcp-server suite 3579/0/35, installer syntax, design skills, JSON, hook timeouts,
platform drift, python). This PR touches no code, so the suite is orthogonal.
v1.6.4/v2.0.0→latest;-rc.1/-beta.2/-alpha.1→next;v1.2.3.4/v1.2.3extra/v1.2/"v1.2.3 ; rm -rf /"/newline-injection→refused.
(lockstep-staleness → fixed by rebase; malformed-tag glob → strict regex;
unprotected-env-auto-create → docs audit block); round 2 APPROVE, no new issues,
injection clean (
TAG_REFviaenv:, not inlined).For Overwatch (admin authority — NOT in this PR)
The workflow references
environment: release, but a referenced environment with noprotection rules auto-creates unprotected — the approval pause silently disappears.
Please confirm (audit block is in the doc):
repos/FerroxLabs/ijfw/environments/releasehas required reviewers ≥ 1;refs/tags/v*creation to the release actor.Out of scope per the card: token/OIDC migration, classic-token cleanup, and the
env-protection + tag-creation settings themselves (admin).