Skip to content

CI leaves a live GCP registry token in .npmrc inside the Docker build context on non-version-bump builds #214

Description

@SanabriaRusso

Problem

.github/workflows/build.yaml writes a live GCP access token into .npmrc in the workspace root:

# .github/workflows/build.yaml:63-66
run: |
  # Only set authentication, not the default registry
  # This allows dependencies to install from public npm
  echo "//europe-southwest1-npm.pkg.dev/o1labs-192920/euro-npm/:_authToken=$(gcloud auth print-access-token)" > .npmrc

The cleanup that removes it is conditional:

# .github/workflows/build.yaml:110-113
if: always() && steps.determine_npm_version.outputs.needs_version_update == 'true'
run: |
  node -e "...restore package.json version..."
  rm -f .npmrc

docker/build-push-action runs later in the same job with context: .. So on any build where needs_version_update is false, .npmrc — containing a valid registry token — is still on disk and is uploaded into the Docker build context.

.dockerignore (added by #189) does not list it:

node_modules
build
.git
.github
.env*
db
data
...

Current exposure

There is no leak today. The Dockerfile never COPYs .npmrc: its only COPY sources are package*.json, src, tsconfig.json, and schema.graphql, and the package*.json glob does not match .npmrc. The file enters the build context but never a published layer.

This is filed as a latent hazard, not an active incident. The failure mode is one careless line away: any future COPY . . or COPY . /app — the most natural thing to write when adding a file to the image — would bake a registry credential into a layer that is pushed to both GAR and GHCR. Defending against that is precisely what .dockerignore is for, and the cost is one line.

Proposed resolution

Primary fix — add to .dockerignore:

 .env*
+.npmrc
 db

I raised this as a non-blocking note on #189 (which introduces .dockerignore). If #189 merges without it, apply it directly to main. Either route is fine; this issue exists so it is not lost.

Secondary fix, recommended — make the cleanup unconditional in .github/workflows/build.yaml. A credential should not survive on disk because an unrelated version-bump branch was not taken:

-        if: always() && steps.determine_npm_version.outputs.needs_version_update == 'true'
+        if: always()
         run: |
-          node -e "const fs = require('fs'); ... pkg.version = '${{ steps.determine_npm_version.outputs.original_version }}'; ..."
           rm -f .npmrc

Keep the package.json version restore on its existing condition — only the rm -f .npmrc needs to become unconditional. Splitting them into two steps is the cleanest way to express that.

Also worth checking: whether the token needs to be in a file at all. npm reads NPM_CONFIG_//registry/:_authToken from the environment, which would keep the credential out of the filesystem and out of every build context by construction. That is a larger change; the two fixes above are sufficient.

Acceptance criteria

  • .npmrc is listed in .dockerignore
  • rm -f .npmrc runs unconditionally in build.yaml
  • Verified: after a build where needs_version_update == 'false', no .npmrc remains in the workspace

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2GA polish / hygieneproduction-readinessWork toward making the API production-ready / publicly available

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions