Skip to content

harden(publish): release-env gate + tag-version assert + dist-tag fix#36

Merged
FerroxLabs merged 1 commit into
mainfrom
feat/publish-hardening
Jul 11, 2026
Merged

harden(publish): release-env gate + tag-version assert + dist-tag fix#36
FerroxLabs merged 1 commit into
mainfrom
feat/publish-hardening

Conversation

@FerroxLabs

@FerroxLabs FerroxLabs commented Jul 11, 2026

Copy link
Copy Markdown
Owner

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)

  1. WHO-can-publish gate — the publish job now runs under environment: release.
    A v* tag, no matter who pushes it, pauses for required-reviewer approval before any
    npm publish. (The environment protection itself is admin-configured in repo
    settings, outside this workflow — see the audit block below.)
  2. Tag ↔ version assert — a pre-publish step fails the job unless the pushed tag
    equals both installer/package.json and mcp-server/package.json versions.
    Combined with the 9-surface version-lockstep gate now in check-all.sh (landed in
    fix(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.
  3. dist-tag fix — an anchored regex validates the tag shape first
    (^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$), then stable→latest, ANY
    prerelease→next, malformed→refused. The old case globs would have routed
    v1.2.3.4 / v1.2.3extra to latest; the old -rc.-only match shipped
    -beta/-alpha to latest. Both closed.
  4. docs/CI-PUBLISH.md — current-pipeline callout: the NPM_TOKEN ~2026-10-08
    (90-day) rotation deadline
    , plus a gh api settings-audit block to verify the
    release-env required reviewer (≥1) and the refs/tags/v* creation ruleset are
    actually applied (the workflow cannot self-enforce either).

Verification

  • check-all.sh green on the rebased tree (banned-char, 9-surface lockstep, full
    mcp-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.
  • dist-tag + assert logic unit-tested across all shapes: 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.
  • Two adversarial review rounds (reviewer tasked to refute): round 1 APPROVE-WITH-NITS
    (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_REF via env:, not inlined).

For Overwatch (admin authority — NOT in this PR)

The workflow references environment: release, but a referenced environment with no
protection rules auto-creates unprotected — the approval pause silently disappears.
Please confirm (audit block is in the doc):

  • repos/FerroxLabs/ijfw/environments/release has required reviewers ≥ 1;
  • a ruleset restricts 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).

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.
@FerroxLabs FerroxLabs changed the title harden(publish): release-env gate + tag<->version assert + dist-tag fix harden(publish): release-env gate + tag-version assert + dist-tag fix Jul 11, 2026
@FerroxLabs FerroxLabs merged commit 4090459 into main Jul 11, 2026
7 checks passed
@FerroxLabs FerroxLabs mentioned this pull request Jul 11, 2026
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