From 36878e1b131951f4d25bbb9ac65ae965ebae4282 Mon Sep 17 00:00:00 2001 From: OB Date: Wed, 20 May 2026 14:19:48 +0700 Subject: [PATCH] docs(readme): document NPM_CONFIG_FILE recommendations + token bootstrap setup Surface the recommended hardened `.npmrc` contents (per-line rationale) and the token bootstrap auth setup as Security subsections: NPM_PACKAGE_REGISTRY_TOKEN + NPM_EXTRA_CONFIG secrets needed to publish the first version of a new scoped package, why npm publish is used on that path, migration token to OIDC after first publish. Clarify in the Environment intro that every npm-publish-related value is a secret (encrypted), not a GitHub var. Extend the wire-up example with the NPM_EXTRA_CONFIG forward so the bootstrap setup works out of the box. Bumps 0.1.11 to 0.1.12 and prepends the changelog entry. --- CHANGELOG.md | 5 +++++ README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f817ec..53dbcad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.1.12 - 20/05/2026 + +### Documentation +- `README` — document the recommended hardened `NPM_CONFIG_FILE` `.npmrc` (per-line rationale) and the token bootstrap auth setup (`NPM_PACKAGE_REGISTRY_TOKEN` + `NPM_EXTRA_CONFIG` secrets, why `npm publish` is used on that path, migration token → OIDC after first publish). Reaffirms that every npm-publish-related value is a **secret** (encrypted), not a GitHub `var`. Reflects the v0.1.11 token-path hardening. + ## v0.1.11 - 20/05/2026 ### Fixes diff --git a/README.md b/README.md index 5820672..5c62d77 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ Section format: `## vX.Y.Z - DD/MM/YYYY`. Idempotent. Reuses an existing hand-cu ## Environment -Zero inputs on pipelines and on every composite — imposed, not proposed. Configuration flows through `secrets:` and the caller's `vars` context. +Zero inputs on pipelines and on every composite — imposed, not proposed. Configuration flows through the caller's `secrets:` block. Every npm-publish-related value is a **secret** (encrypted at rest, masked in logs); none of them are GitHub `vars`.
Secrets (caller's secrets: block) @@ -175,7 +175,7 @@ Zero inputs on pipelines and on every composite — imposed, not proposed. Confi | `NPM_EXTRA_CONFIG` | | Extra `.npmrc` lines appended after `NPM_CONFIG_FILE`. A **secret** — it lands in `.npmrc`, so it can carry auth material and must stay masked. | | `NPM_PACKAGE_REGISTRY` | ✔ | npm package registry URL. | | `NPM_PACKAGE_PROXY_REGISTRY` | | Optional npm proxy registry URL. | -| `NPM_PACKAGE_REGISTRY_TOKEN` | | Required for token-based publish to private registries. Absent → OIDC. | +| `NPM_PACKAGE_REGISTRY_TOKEN` | | npm Granular Access Token, scoped to the publishing organization with create-new-package permission. Required only for the token bootstrap (first publish of a new scoped package, before npm Trusted Publisher is bound). Absent → OIDC. |
@@ -233,14 +233,58 @@ pnpm CLI resolved via corepack from `packageManager`. No floating version reache
-Publish — OIDC vs token auth +Recommended NPM_CONFIG_FILE contents
-Auto-detected by `NPM_PACKAGE_REGISTRY_TOKEN` presence: +Minimal hardened `.npmrc` for every Coroboros consumer. Stored as a **secret** (encrypted; carries `${VAR}` expansions resolved at install time): -- **Absent** → `pnpm publish --provenance --no-git-checks` (OIDC Trusted Publisher + provenance attestation, no long-lived token). -- **Present** → `pnpm publish --no-git-checks` (token-based via the `.npmrc` generated by `javascript/base`). +```ini +@coroboros:registry=https:${NPM_PACKAGE_REGISTRY} +save-exact=true +fund=false +audit=false +ignore-scripts=true +package-lock=false +prefer-online=true +``` + +| Line | Why | +| :--- | :--- | +| `@coroboros:registry=https:${NPM_PACKAGE_REGISTRY}` | Scope-resolved registry — `${NPM_PACKAGE_REGISTRY}` expands from the same-named secret. | +| `save-exact=true` | Pin exact versions on `add` / `install`. | +| `fund=false` | Suppress funding noise in CI logs. | +| `audit=false` | `osv-scanner` (in `security.yml`) covers vulnerability scans natively. | +| `ignore-scripts=true` | Belt-and-suspenders against postinstall supply-chain attacks — backs up the `--ignore-scripts` flag already passed by `javascript/base` on every `pnpm install`. | +| `package-lock=false` | Prevent `npm` from emitting a parasitic `package-lock.json` in pnpm repos. | +| `prefer-online=true` | Re-fetch dep metadata each install — local cache cannot mask a yanked or republished version. | + +
+ +
+Publish — OIDC vs token bootstrap + +
+ +Auto-detected by `NPM_PACKAGE_REGISTRY_TOKEN` **secret** presence on the consumer repo: + +| Token secret | Mode | Command | +| :--- | :--- | :--- | +| absent | **OIDC + provenance** (default) | `pnpm publish --provenance --no-git-checks` | +| present | **Token bootstrap** | `npm publish --ignore-scripts --access public` | + +**OIDC + provenance** — no long-lived token in the repo; npm trusts a per-run id-token issued by GitHub Actions for `coroboros//ci.yml`. Requires the npm Trusted Publisher form, which only accepts an existing package — so the very first publish has to take the token bootstrap below. + +**Token bootstrap** — publishes the first version of a new scoped package. Set two additional **secrets** on the consumer (encrypted; forwarded via the caller's `secrets:` block): + +| Secret | Contents | +| :--- | :--- | +| `NPM_PACKAGE_REGISTRY_TOKEN` | npm Granular Access Token scoped to the publishing organization with create-new-package permission. Long-lived; revoke after migrating to OIDC. | +| `NPM_EXTRA_CONFIG` | `${NPM_PACKAGE_REGISTRY}:_authToken=${NPM_PACKAGE_REGISTRY_TOKEN}` — appended to `.npmrc` by `javascript/base`. Stored as a **secret** because it carries auth expansion. | + +`npm publish` is used on the bootstrap path (not `pnpm publish`) because pnpm `>= 11.1.3` in CI auto-attempts the OIDC token exchange and does not fall back to the `.npmrc` token if OIDC fails. `--ignore-scripts --access public` skips publish-time lifecycle hooks (`prepublishOnly` excepted — known `npm` behavior). The published tarball is identical to `pnpm publish`'s. + +After the first publish, configure the npm Trusted Publisher form (Publisher type: GitHub Actions; Organization: the publishing org; Repository: consumer repo; Workflow filename: `ci.yml`; Environment: empty), then open a `chore(ci):` PR dropping `NPM_PACKAGE_REGISTRY_TOKEN` + `NPM_EXTRA_CONFIG` from the caller's `secrets:` block. Revoke the npm token. `1.0.1+` publishes via OIDC + provenance.
@@ -306,6 +350,8 @@ jobs: NPM_CONFIG_FILE: ${{ secrets.NPM_CONFIG_FILE }} NPM_PACKAGE_REGISTRY: ${{ secrets.NPM_PACKAGE_REGISTRY }} NPM_PACKAGE_PROXY_REGISTRY: ${{ secrets.NPM_PACKAGE_PROXY_REGISTRY }} + # Token bootstrap (drop both after npm Trusted Publisher is wired — see Security): + NPM_EXTRA_CONFIG: ${{ secrets.NPM_EXTRA_CONFIG }} NPM_PACKAGE_REGISTRY_TOKEN: ${{ secrets.NPM_PACKAGE_REGISTRY_TOKEN }} ``` diff --git a/package.json b/package.json index 0a19b90..0835a9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coroboros/ci", - "version": "0.1.11", + "version": "0.1.12", "private": true, "description": "Reusable GitHub Actions CI for the Coroboros stack.", "license": "SEE LICENSE IN LICENSE.md",