Skip to content

feat: transient private-registry auth for deploy_component#1158

Draft
kriszyp wants to merge 7 commits into
mainfrom
kris/registry-deploy-auth-core
Draft

feat: transient private-registry auth for deploy_component#1158
kriszyp wants to merge 7 commits into
mainfrom
kris/registry-deploy-auth-core

Conversation

@kriszyp

@kriszyp kriszyp commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds optional registryAuth: [{ registry, token, scope? }] to deploy_component, letting a deploy authenticate npm pack/install against a private registry for package=npm:@org/app@x.y.z references.
  • The token is written to a transient 0600 .npmrc (in a 0700 tmpdir), injected via npm_config_userconfig for the deploy's npm spawns, and removed on completion. It is stripped from the operation before replication and from the operations log — so it never persists on disk, reaches peers, lands in logs, or enters the package reference / harperdb-config.yaml / hdb_deployment row.

Context / why

Deploy-by-reference (vs. payload) is cleanly recorded, revertible, and ships no blob — but a private registry needs npm auth, which previously had to be hand-placed per host. This adds a managed, transient credential path. In the cluster, the Harper fabric injects NPM_CONFIG_USERCONFIG on peers; this PR is the data-plane half that handles an origin-supplied token without durably persisting it.

Where to focus

  • Security flow (components/operations.js): delete req.registryAuth runs unconditionally before replicateOperation; serverHelpers/serverUtilities.ts strips it from the ops log.
  • Transient .npmrc lifecycle (components/Application.ts): writeTransientNpmrc/cleanupTransientNpmrc, 0600 file / 0700 dir, npm_config_userconfig injection in nonInteractiveSpawn.
  • Two design decisions worth a second opinion:
    1. A scope-less entry sets npm's default registry= (so an unscoped spec resolves against the private registry) — which requires that registry to serve/proxy public deps. Scoped entries route only their @scope.
    2. writeTransientNpmrc prepends any inherited npm_config_userconfig (e.g. a fabric-injected file) so its proxy/cafile/other registries survive, appending the transient auth last (npm last-value-wins).

Paired with the harper-pro wrapper PR (log redaction + this core bump). Unit coverage added for buildNpmrcContent, the .npmrc lifecycle/merge, and the deploy_component validator.

🤖 Generated by Claude (Opus 4.7).

kriszyp and others added 2 commits June 7, 2026 18:20
Add an optional `registryAuth` array to deploy_component carrying private npm
registry tokens. The deploying node materializes a per-deploy 0600 `.npmrc`
(in a 0700 temp dir) that `npm pack`/`npm install` authenticate against, then
removes it; the token is held only in memory and that transient file. The token
is stripped from the request before replication and from the operations log, so
it never persists to config, hdb_deployment, the replication channel, or logs.
Peers reinstall the package via their own fabric-injected NPM_CONFIG_USERCONFIG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d npmrc

Addresses two Codex review findings on the transient registry-auth path:

- A scope-less registryAuth entry now also emits a default `registry=` line so an
  unscoped package spec (npm:my-private-app) and its transitive deps resolve against
  the supplied private registry instead of silently falling back to npmjs (the token
  would otherwise never be used). Scoped entries still route only their @scope.
- writeTransientNpmrc now prepends any inherited npm_config_userconfig (e.g. a
  fabric-injected file with cluster registries, proxy, or cafile) and appends the
  transient auth last so it wins on conflict, instead of clobbering those settings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces transient private-registry authentication for deploying components by writing credentials to a temporary .npmrc file during installation and cleaning them up immediately afterward. The code reviewer provided valuable feedback to enhance security and robustness, including deleting sensitive credentials immediately after instantiation to prevent leakage on failure, wrapping the cleanup directory removal in a try-catch block to avoid masking deployment errors, and adding defensive checks to prevent directory leaks if the initialization is called multiple times.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread components/operations.js Outdated
Comment thread components/Application.ts
Comment thread components/operations.js Outdated
Comment thread components/Application.ts
@claude

claude Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

@kriszyp kriszyp requested review from Ethan-Arrowood and heskew June 8, 2026 12:30
kriszyp and others added 2 commits June 8, 2026 07:32
Address cross-model (gemini) review findings on the private-registry deploy
auth path, all defense-in-depth for the invariant that the token never
survives into a log/error path or replication:

- operations.js: strip req.registryAuth immediately after the Application ctor
  captures it, instead of after loadComponent. The prior strip ran only on the
  success path, leaking the token in req if prepareApplication/loadComponent
  threw. Removes the now-redundant later delete.
- Application.cleanupTransientNpmrc: wrap rm in try/catch so a failure (e.g. a
  Windows file lock) can't mask the original deploy error or skip
  broadcastDeployEnd; state is always cleared in finally.
- Application.writeTransientNpmrc: clean up a prior temp dir if called twice, so
  the earlier 0700 dir + token file isn't leaked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread components/operationsValidation.js
Comment thread components/Application.ts
Address claude[bot] review findings on the private-registry deploy auth path:

- operationsValidation.js: forbid CR/LF in registry and token. Both are written
  verbatim into the transient line-based .npmrc, so a super_user could otherwise
  inject arbitrary npm config lines (redirect scopes/registries, set other keys).
  Uses a newline guard rather than a strict URI validator because registry also
  accepts bare hosts and //host/ forms. Adds tests for both injection paths plus
  a bare-host case to pin that the guard doesn't over-restrict.
- Application.cleanupTransientNpmrc: also clear this.registryAuth so the
  plaintext token array can't surface in a later heap dump or error serialization
  of the Application instance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kriszyp kriszyp marked this pull request as ready for review June 8, 2026 14:59
kriszyp added 2 commits June 8, 2026 15:46
…auth-core

# Conflicts:
#	components/operationsValidation.js
#	unitTests/server/fastifyRoutes/operationsValidation.test.js
@kriszyp

kriszyp commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

FAB-356 alignment note (cc @heskew, since you own FAB-356 and review here)

This PR and the GitHub-App private-repo design share the same core invariant — keep the deploy credential out of the replicated operation body / WAL — so they're complementary, not competing (npm npm: refs vs https://github.com/… git clones, alongside the existing git+ssh:// path). Two overlaps worth capturing now so the F-* work lands cleanly on top of this:

  1. Spawn hardening (F-5). This PR injects npm_config_userconfig into the shared nonInteractiveSpawn, which runs shell: true (components/Application.ts ~L827/L835). F-5 plans to fork deploy-time spawns into a shell: false nonInteractiveSpawnDeploy precisely to close the credential-bearing-env + shell:true RCE class. When that fork lands, the npmUserconfigPath threading needs to move onto the hardened deploy helper rather than stay on the legacy shell:true one — otherwise private-registry npm pack/install auth gets stranded. (Lower exposure than the git case here — npm_config_userconfig is a file path, not a free-form URL — but same risk class, so it should ride the same fix.)

  2. Shared deployComponent surface (F-4). This PR already does strip-before-replicate (delete req.registryAuth before replicateOperation) and uses the token only locally on the originator. F-4's originator/peer branch + identity-only req.credentials object touches the same components/operations.js#deployComponent, so it'd be good to write F-4 knowing registryAuth is already handled this way — the two should compose rather than collide.

Nothing actionable for this PR; just flagging so the F-1/F-4/F-5 work accounts for the npm path. Happy to pair on the nonInteractiveSpawnDeploy fork when it comes up.

— Claude

@kriszyp kriszyp marked this pull request as draft June 11, 2026 20:03
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