Skip to content

ci: move the GitHub Packages credential out of the project .npmrc - #5

Merged
EliRobinson merged 1 commit into
mainfrom
ci/github-packages-credential
Aug 13, 2026
Merged

EliRobinson merged 1 commit into
mainfrom
ci/github-packages-credential

Conversation

@EliRobinson

Copy link
Copy Markdown
Owner

Moves the GitHub Packages credential out of the committed .npmrc and into a source pnpm trusts at every version. Split out of #4 so a CI-auth regression can be reverted on its own.

The trap

The repo-root .npmrc carried the credential as an env placeholder:

//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

pnpm 10 stopped expanding environment variables in registry credentials read from a project .npmrc — that file is committed, so a hostile registry line could exfiltrate the token. The result is a version-dependent split:

behavior
pnpm 9.15.0 (the pin in packageManager) expands it — CI works today
pnpm 10+ ignores it — resolves to nothing

So the line works in CI, warns on every local command (Homebrew pnpm 11 shells out to the pinned 9.15.0, and the outer binary parses .npmrc first), and the day packageManager moves to pnpm 10 it silently stops authenticating. The failure mode then is a bare 401 from the registry that names no cause.

Confirmed the credential is genuinely required — the registry returns HTTP 401 unauthenticated, and CI has no other credential source.

The change

The scope→registry mapping stays in the project .npmrc; that's what it's for. Only the credential moves.

  • CI writes the NODE_AUTH_TOKEN secret into the runner's ~/.npmrc in an explicit step before install, in all three jobs.
  • Locally it becomes a one-time pnpm config set "//npm.pkg.github.com/:_authToken" <PAT> --global, replacing the per-shell export $(grep ... .env.local) that pnpm 10+ would ignore anyway. Verified this writes to ~/.npmrc.

Secret scoping

NODE_AUTH_TOKEN moves from job-level env to the auth step alone. It's now absent from the environment during pnpm install — where dependency lifecycle scripts run — and during the test and build steps. No other step needed it.

Docs

.env.example previously advertised NODE_AUTH_TOKEN as a value to fill in. It now explains why the credential doesn't belong in that file, so the next person doesn't re-add a line that cannot work. README gains an "Authenticate to GitHub Packages" step; AGENTS.md records the pnpm 10 rule as the reason.

Verification

  • pnpm install --frozen-lockfile succeeds with the credential absent from the project .npmrc, and the warning is gone.
  • Workflow YAML parses; all three jobs place the auth step immediately before their install, with the secret scoped to that step.
  • CI on this PR is the real test of the runner path — it installs the private packages with the project .npmrc carrying no credential.

The repo-root .npmrc carried the credential as an env placeholder:

  //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

pnpm 10 stopped expanding environment variables in registry credentials that
come from a project .npmrc — the file is committed, so a hostile registry line
could exfiltrate the token. The placeholder is honoured by the pinned pnpm
9.15.0 and ignored by anything newer, which is a trap: local installs warn on
every command today, and the day `packageManager` moves to pnpm 10 the
credential silently resolves to nothing and CI fails with a bare 401 that names
no cause.

The registry mapping stays in the project .npmrc, which is what it is for. The
credential moves to a source pnpm still trusts at every version:

  - CI writes the NODE_AUTH_TOKEN secret into the runner's ~/.npmrc in an
    explicit step before install.
  - Locally it is a one-time `pnpm config set ... --global`, replacing the
    per-shell `export $(grep ... .env.local)` that pnpm 10+ would ignore
    anyway.

The secret is now scoped to the auth step rather than the whole job, so it is
absent from the environment during `pnpm install` — where dependency lifecycle
scripts run — and during the test and build steps. No job needed it elsewhere.

.env.example previously advertised NODE_AUTH_TOKEN as a variable to set; it now
says why the credential does not belong in that file at all, so the next person
does not re-add a line that cannot work.

Verified: `pnpm install --frozen-lockfile` succeeds with the credential absent
from the project .npmrc and emits no warning; the workflow parses and all three
jobs place the auth step immediately before their install.
@EliRobinson
EliRobinson merged commit 1a6804f into main Aug 13, 2026
3 checks passed
@EliRobinson
EliRobinson deleted the ci/github-packages-credential branch August 13, 2026 05:26
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