Add --publish-only gate to narrow NPM_TOKEN exposure#16
Merged
jgowdy-godaddy merged 1 commit intomainfrom Apr 17, 2026
Merged
Conversation
By default every npmenc invocation injects NPM_TOKEN* into the npm process's environment, which npm then inherits to every lifecycle script (preinstall / install / postinstall) of every transitive dependency. That's the Type-2 lifecycle-script risk called out as the number-one residual in THREAT_MODEL.md. Add an opt-in --publish-only flag that strips NPM_TOKEN* from the child env unless the subcommand is one that actually authenticates to the registry (publish, unpublish, deprecate, access, owner, team, dist-tag, whoami, profile, token, hook, org, adduser, login, signup, logout, star). For those commands tokens still flow as before. For install/ci/run-script/version/etc. no token is set — trading away private-registry reads to close the exposure window on the commands most likely to execute untrusted JavaScript with the token in scope. Subcommand detection uses a positive scanner: args are walked left to right; the first one matching a known auth or non-auth subcommand wins. Unknown args are treated as flag values and skipped. An unrecognized / custom subcommand defaults to 'needs auth' to avoid silently breaking user workflows. Ambiguous cases where a flag value collides with a subcommand name (e.g. --loglevel info) are documented — users can avoid the edge by writing --flag=value. npxenc does not expose --publish-only: npx invokes package code whose registry-auth needs we can't predict, so it always injects. Tests cover the positive / negative subcommand matching, flag-value skipping, empty/unknown fallback, npx always-true, and the strip helper's NPM_TOKEN prefix filter. THREAT_MODEL.md 'injection when not needed' section updated to describe the mitigation and document the known limitation.
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.
Summary
Addresses the number-one residual risk in `THREAT_MODEL.md`: every `npmenc` invocation injects `NPM_TOKEN*` into npm's environment by default, which npm then inherits to every lifecycle script (`preinstall` / `install` / `postinstall`) of every transitive dependency. A malicious transitive dep can `console.log(process.env.NPM_TOKEN_*)` and exfiltrate the token during a routine `npm install`.
Add an opt-in `--publish-only` flag that strips `NPM_TOKEN*` from the child environment unless the subcommand is one that actually authenticates to the registry:
```
publish, unpublish, deprecate, undeprecate,
access, owner, team, dist-tag, dist-tags,
whoami, profile, token, hook, hooks, org,
adduser, add-user, login, signup, logout,
star, unstar, stars
```
For those commands tokens still flow as before. For `install`, `ci`, `run-script`, `version`, etc., no token is set — the user explicitly trades away private-registry reads to close the exposure window on the commands most likely to execute untrusted JavaScript.
Detection heuristic
Positive scanner walks `args` left-to-right; the first arg matching a known auth or non-auth subcommand wins. Unknown args (likely flag values) are skipped. An unrecognized / custom subcommand defaults to "needs auth" to avoid silently breaking user workflows.
The known limitation: if a flag value happens to collide with a subcommand name (e.g. `--loglevel info` where `info` is an alias for `view`), the scanner may find the value first. Users can avoid this by writing `--flag=value` form. Covered by test `needs_auth_ambiguous_flag_value_collides_with_subcommand_name`.
Scope
Test plan