npmenc and npxenc are direct-launch wrappers for npm and npx that adapt tools which
expect credentials to live on disk.
They keep ordinary wrapped execution ephemeral, support an explicit install / uninstall
lifecycle for persistent .npmrc conversion, and incubate a reusable application-adaptation
library (enclaveapp-app-adapter) inside this repo before that abstraction is promoted back into
libenclaveapp.
This workspace has four crates:
enclaveapp-app-adapter- generic executable resolution, direct launch, temp-config handling, binding storage, and encrypted secret storage
npmenc-core- npm-specific
.npmrcparsing, token-source policy, wrapper preparation, install, and uninstall
- npm-specific
npmenc- CLI wrapper for
npm
- CLI wrapper for
npxenc- CLI wrapper for
npx
- CLI wrapper for
The main security properties are:
- the final
npm/npxprocess is always direct-launched by the wrapper - the shell may be used as a resolution oracle, but never as the secret-bearing launcher
- temp config files for wrapped execution contain placeholders like
${NPM_TOKEN_DEFAULT}, not raw secrets - secrets are stored encrypted at rest
token listshows metadata only- there is no
token get - ambiguous or corrupted managed state fails fast instead of being heuristically reinterpreted
Both npmenc and npxenc expose the same management commands against the same state:
installuninstalltoken settoken addtoken listtoken deleteregistry addregistry set-defaultregistry listregistry remove
Wrapper flags:
--userconfig--shell--resolve-mode auto|path-only|command-v--npm-bin/--npx-bin--dry-run--print-effective-config--strict--allow-unscoped-auth--auto-install
Requirements:
- Rust 1.75+
- access to the sibling
../crates/enclaveapp-app-storagecrate fromlibenclaveapp
Build everything:
cargo buildRun tests and linting:
cargo test
cargo clippy --workspace --all-targetsStore a default npm registry token as managed state:
cargo run -p npmenc -- token set --secret 'npm_...'Wrap an npm invocation:
cargo run -p npmenc -- -- installWrap an npx invocation:
cargo run -p npxenc -- -- cowsay helloInspect the effective rewritten config without launching:
cargo run -p npmenc -- --dry-run -- --versionDefault registry binding:
npmenc token set --secret 'npm_...'Custom registry binding:
npmenc registry add \
--label mycompany \
--url https://artifactory.example.com/api/npm/npm/ \
--secret 'eyJ...'List metadata:
npmenc token listtoken list and registry list validate token-source state against persisted sidecars. Corrupt
managed token-source state is reported as an error instead of being shown as healthy metadata.
Delete a binding that is not currently installed into any config:
npmenc token delete --label mycompanyImportant invariants:
defaultis reserved forhttps://registry.npmjs.org/- only one binding may target a normalized registry auth key
- installed bindings cannot be deleted until they are uninstalled from every attached config
There are two token-source classes:
- command sources
- provider sources
Preferred CLI forms:
npmenc token set --token-command 'source-token --audience npm'
npmenc token set --token-provider sso-jwt
npmenc token set --token-provider sso-jwt --token-handle corp/prodRaw --token-source is still supported for explicit or otherwise unambiguous forms:
npmenc token set --token-source 'command:source-token --audience npm'
npmenc token set --token-source 'provider:sso-jwt:corp/prod'
npmenc token set --token-source '/opt/bin/source-token'Ambiguous bare one-token values are intentionally rejected:
# rejected
npmenc token set --token-source sso-jwtWhy: bare one-token specs were historically ambiguous between command and provider semantics. The CLI now forces that distinction to be explicit.
Built-in provider:
sso-jwt
Generic provider helpers are also supported through the generic provider runtime. Those helpers are invoked with:
NPMENC_TOKEN_PROVIDER_PROTOCOL=v1NPMENC_TOKEN_PROVIDER_REQUEST_JSON=<json>
The request JSON contains provider and handle.
If a provider cannot directly reacquire a token in the current environment, you can still store it as metadata together with an explicit secret:
npmenc token set \
--secret 'stored_token' \
--token-source 'provider:corp-provider:prod'Ordinary wrapped execution does not rewrite your real .npmrc.
Instead, npmenc / npxenc:
- resolve the effective
.npmrc - parse auth entries
- build an effective config
- prepare the least-secret-exposing launch plan
- inject env vars and launch the real target directly
Observed wrapper modes:
PassthroughManagedBindingsTransientFallback
TransientFallback means raw token material was discovered in .npmrc and used for this
invocation even though managed install state is not yet in place.
install intentionally rewrites the persistent effective .npmrc into managed placeholder form.
Typical install behavior:
- import scoped
:_authTokenlines - optionally import unscoped
_authTokenwith--allow-unscoped-auth - leave unsupported legacy auth forms unchanged with warnings
- record per-config provenance for later uninstall
Example:
npmenc --userconfig ~/.npmrc installuninstall restores away from placeholder-managed config:
- source-origin managed entries are materialized back into
.npmrc - appended managed auth lines are removed
uninstallpurges managed bindings and secrets by defaultuninstall --keep-secretsremoves config integration but keeps the managed secret state
Examples:
npmenc --userconfig ~/.npmrc uninstall
npmenc --userconfig ~/.npmrc uninstall --keep-secrets--strict turns warnings about unsupported or unsafe auth state into failures.
Current policy highlights:
- registry-scoped
:_authTokenis the primary supported auth form - legacy auth forms like
_auth,_password, andusernameare diagnosed but not migrated - managed placeholder state without matching managed storage is treated as invalid / corrupt state
- old ambiguous legacy token-source state is treated as corruption and must be repaired explicitly
Inspection commands are read-only:
--dry-run--print-effective-config
They do not:
- acquire secrets
- initialize the encrypted secret backend
- create managed state as a side effect
If the managed state is dirty or inconsistent, inspection fails instead of guessing through it.
State is stored under the app name npmenc.
Default:
- platform config dir, usually
~/.config/npmenc
Override for testing or isolation:
export NPMENC_CONFIG_DIR=/tmp/npmenc-devPrimary files:
bindings.jsonsecrets/state.lockstate.version
This repo deliberately incubates the reusable adapter layer locally first. The intended next
architectural step is to promote enclaveapp-app-adapter back into libenclaveapp once the
abstraction is considered stable across more consumers.
For the detailed current implementation design, see DESIGN.md.