Skip to content

fix(security): keep the git checkout out of the published image - #13

Merged
IKatsuba merged 2 commits into
mainfrom
fix/docker-context-credential-leak
Aug 8, 2026
Merged

fix(security): keep the git checkout out of the published image#13
IKatsuba merged 2 commits into
mainfrom
fix/docker-context-credential-leak

Conversation

@IKatsuba

@IKatsuba IKatsuba commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Problem

The Dockerfile used COPY . . and the repository had no .dockerignore, so the whole build context landed in an image layer — including .git.

actions/checkout defaults to persist-credentials: true, which writes the job's ephemeral GITHUB_TOKEN into .git/config:

[http "https://github.com/"]
	extraheader = AUTHORIZATION: basic <base64 x-access-token:TOKEN>

So every published image carried that token, readable by anyone who pulled the image while the token was still valid — a window running from the image push to the end of the publish job. In that window the token grants packages: write on ghcr.io/ikatsuba/* (the publish job scopes its permissions, so it is not the full default set).

Confirmed present in ghcr.io/ikatsuba/nx-cache-server:main. Tokens in already-published images are expired, so there is nothing to rotate.

The same COPY . . also shipped .env.local, the test suites, .github/, editor configs, and — on any build after #11 — the TLS test fixtures including src/fixtures/tls/key.pem.

Fix

  • Dockerfile — copy only deno.json/deno.lock (ahead of deno install, which also keeps the dependency layer cached) and src/index.ts.
  • .dockerignore (new) — deny-all plus an allow-list of those same paths, so a future COPY . . cannot reintroduce the leak.
  • .github/workflows/main.ymlpersist-credentials: false on all five checkout steps. No job needs git credentials after checkout; helm-publish authenticates to GHCR explicitly.

Verification

  • Built the image: /app contains only deno.json, deno.lock, src/index.ts. A grep across the filesystem for extraheader and the test AWS key returns nothing.
  • Container starts and GET /health returns 200.
  • deno fmt --check and deno lint pass.

Reported privately by email. Follow-up worth doing separately: add a SECURITY.md so reports have a documented channel.

`COPY . .` with no `.dockerignore` baked the entire build context into
the image layer, including `.git`. `actions/checkout` defaults to
`persist-credentials: true`, which writes the job's ephemeral
`GITHUB_TOKEN` into `.git/config` as `http.extraheader`, so every
published image shipped a copy of that token — readable by anyone who
pulled the image while the token was still valid.

- Dockerfile: copy only `deno.json`/`deno.lock` (before `deno install`,
  which also keeps the dependency layer cached) and `src/index.ts`.
- Add a deny-all `.dockerignore` allow-listing those same paths, so a
  future `COPY . .` cannot reintroduce the leak.
- Set `persist-credentials: false` on every checkout; no job needs git
  credentials after checkout.

This also stops `.env.local`, the test suite and the TLS test fixtures
(including `src/fixtures/tls/key.pem`) from being shipped.
@IKatsuba
IKatsuba force-pushed the fix/docker-context-credential-leak branch from dbae7a6 to 359c6de Compare August 8, 2026 21:31
@IKatsuba
IKatsuba merged commit 6beff69 into main Aug 8, 2026
5 checks passed
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