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:
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
Problem
.github/workflows/build.yamlwrites a live GCP access token into.npmrcin the workspace root:The cleanup that removes it is conditional:
docker/build-push-actionruns later in the same job withcontext: .. So on any build whereneeds_version_updateis 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:Current exposure
There is no leak today. The
DockerfileneverCOPYs.npmrc: its onlyCOPYsources arepackage*.json,src,tsconfig.json, andschema.graphql, and thepackage*.jsonglob 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 . .orCOPY . /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.dockerignoreis for, and the cost is one line.Proposed resolution
Primary fix — add to
.dockerignore:.env* +.npmrc dbI raised this as a non-blocking note on #189 (which introduces
.dockerignore). If #189 merges without it, apply it directly tomain. 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:Keep the
package.jsonversion restore on its existing condition — only therm -f .npmrcneeds 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.
npmreadsNPM_CONFIG_//registry/:_authTokenfrom 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
.npmrcis listed in.dockerignorerm -f .npmrcruns unconditionally inbuild.yamlneeds_version_update == 'false', no.npmrcremains in the workspace