kit 6.6.5
Changed
-
Token publishing is now disallowed on all 13 packages. With 6.6.5-rc.1 proving the OIDC
path, every package's npm "Publishing access" moved to require two-factor authentication and
disallow bypass 2fa tokens — the setting that turns "we also have OIDC" into "only OIDC can
publish". Each was read back from its own settings page. There is no token fallback left, so
a misconfigured trusted publisher now fails that package's publish step and is fixed in npm's
UI rather than worked around; the workspace loop is idempotent, so re-running completes the
set.docs/RELEASING.mdrecords the operational cost: npm's step-up 2FA is flaky under
repetition,Update Package Settingsis a no-op unless the radio actually changed, and the
CLI equivalent (npm access set mfa=publish) needs a TOTP code that a security-key-only
account cannot produce. -
Publishing runs on trusted publishing (OIDC); the npm token is gone. All 13 packages
now carry a GitHub Actions trusted publisher namingsandstream/kit,publish.ymland the
npm-publishenvironment, with the single permissionnpm publish— not staged publish,
because the job publishes directly and least privilege is the point.NODE_AUTH_TOKENis
removed from both publish steps, which is what actually switches the credential: npm prefers
a token whenever one is present, so the two cannot both be in effect. That also makes a
re-added token a SILENT downgrade back to the credential npm is retiring, so
src/publish-workflow.test.tsnow fails if any executing line in the workflow carries one.
The environment is no longer merely the guard around a secret — it is part of the credential,
since npm only mints one for runs that reach it.Two things worth knowing for the next package, both learned the hard way: npm requires
step-up 2FA (security key) per save and does not keep the session elevated, so it is one
key tap per package and a timed-out prompt loses that save (nothing half-saves — retrying is
free); and driving the UI quickly trips Cloudflare's bot check, which only a human can clear.
Added
-
The
install-script grantscheck now asks whether the granted package was ever
triaged. #475 deferred this on the grounds that no triage ledger existed to consult. That
was wrong:.kit-triage.jsonlhas been there all along —kit triageappends a PASS to it
and the pre-commitcheck-depsgate reads it. The shape audit can only say how BROAD a
grant is; this asks the question shape cannot answer, and the answers rank differently. A
pinned grant for a package nothing has ever evaluated now warns at high, above an
unpinned grant for one kit cleared yesterday. A grant whose only triage is older than the
freshness window is reported separately, because "reviewed a month ago" and "never reviewed"
are not the same claim.TRIAGE_MAX_AGE_DAYSis now exported and imported rather than
copied, socheck-depsand this check cannot drift to different windows.When the log is absent — most repos have never run
kit triage— the row says the
cross-check did not run instead of reporting every grant as untriaged. A confident finding
built on an absent file is the failure mode this codebase keeps catching in itself. A torn
append is skipped rather than allowed to fail the check. -
scripts/trusted-publishing-wizard.sh— the npm OIDC migration, walked. The
registry side of trusted publishing can only be done in a browser, one package at a time:
npm allows exactly one trusted publisher per package, andnpm accesson 11.19.0 covers
status/collaborators/grant/revoke with no read or write path for the setting. So the wizard
is honest about what it cannot verify and rigorous about the rest. It derives the package
list from the repo rather than carrying a copy — a hardcoded list drifts the moment someone
adds a plugin, and a missed package is the one failure this migration must not have, since
no token plus no trusted publisher means that package's publish step fails. Progress is
recorded in.kit/trusted-publishing.state, so it is resumable across sessions; the
token-removal stage refuses to run until every package is confirmed; and the final stage
points at a prerelease (routed tonext, solatestcannot move) as the only honest test.
Itspublish.ymledit is fail-closed — it removes the parentenv:when the token was its
only child, refuses if any executing line still referencesNPM_TOKEN, and lists the
comment lines that still describe the token so the prose is fixed in the same PR. -
A dialect gate for that script (
src/wizard-script.test.ts). Nothing in this repo lints
shell —lintandformat:checkare TypeScript-only and CI has no shellcheck — so a
committed wizard could be broken in ways no gate notices until the human runs it, during a
credential migration, once. Two such bugs were found by hand while writing it and are now
pinned:mapfile(a bash 4 builtin; macOS ships bash 3.2.57 and the publishing machine is a
Mac) andsed -E '…+?…'(POSIX ERE has no lazy quantifier — macOS sed exits with
"repetition-operator operand invalid"). Both parse fine to the eye and tobash -n. The
gate was mutation-tested by reintroducing each.
Fixed
- A second gate that could not fail: the security-headers step.
security.ymlread
if grep -rE "Content-Security-Policy|…" src/ packages/ | head -1; then—headexits 0 on
empty input, so the condition was always true,Security headers configuredprinted
unconditionally, and the::warning::branch was unreachable. Measured against a
guaranteed-absent pattern before fixing. Same class as the 6.6.4 tag-signature gate, and
found by the rule that fix installed. R1-fail-open-cinow names the class instead of the instance. It flagged a condition
piped intoteeorcat; it now flags any condition whose pipeline ENDS in a status-blind
sink (tee,cat,head,tail,wc,sort,uniq,tr).grep -qstays allowed —
there the pipe's last command IS the question. Adding the rule made kit's own self-audit
fail onsecurity.yml, which is how the second instance surfaced.
Changed
- The security-headers check is now honest, not merely correct. kit is a CLI + MCP (stdio)
tool — the same "no HTTP surface" that skips Stage 3 DAST — so a missing CSP is not a
finding here, and a permanently-expected warning would only train people to ignore warnings.
The check is gated on an HTTP surface actually existing (createServer,express(,
fastify(,Bun.serve,.listen(— each matching 0 lines when written) and fires the day
one lands. All three branches were driven in a fixture tree; the two that mattered had never
been reachable.
Full changelog: https://github.com/sandstream/kit/blob/v6.6.5/CHANGELOG.md
Verify this release:
git tag -v v6.6.5
npm audit signatures