diff --git a/.dockerignore b/.dockerignore index 5414d56..d8b9731 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,49 @@ +# .dockerignore does NOT use .gitignore semantics. Docker matches with +# moby/patternmatcher: filepath.Match plus `**`, so `*` does not cross +# `/` and an unprefixed pattern is anchored at the context root. Every +# depth-independent pattern therefore needs `**/`, or `config/.env` and +# `certs/server.key` still ship while this file reads as solved. Only +# genuinely root-anchored entries go unprefixed. Never transplant these +# into .gitignore, where `**/` is wrong. +# +# Matching is case-sensitive, so secrets use character ranges rather +# than an ALL-CAPS twin, which would still miss `Server.Key`. +# +# Extend with this repo's own host-built artifacts, written anchored: +# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and +# deletes the package directory from the context. .git -node_modules -.DS_Store + +# Environment files. `*.env` covers bare `.env` and the `prod.env` +# convention. Re-include a committed template with a negation if the +# build needs one: `!docs/example.env`. +**/*.[eE][nN][vV] +**/.[eE][nN][vV].* +**/.[eE][nN][vV][rR][cC] + +# Private keys and the bundles carrying them. Public certificates +# (*.crt, *.cer) are deliberately absent: they are legitimate inputs. +**/*.[pP][eE][mM] +**/*.[kK][eE][yY] +**/*.[pP]12 +**/*.[pP][fF][xX] +**/[iI][dD]_[rR][sS][aA] +**/[iI][dD]_[dD][sS][aA] +**/[iI][dD]_[eE][cC][dD][sS][aA] +**/[iI][dD]_[eE][dD]25519 + +# Dependencies: restored inside the image, never copied in. +**/node_modules + +# OS metadata. +**/.DS_Store +**/Thumbs.db + +# Editor state: never a build input, and it churns COPY. +**/*.swp +**/*.swo +**/*~ +**/*.bak +**/.idea +**/.vscode +**/*.sublime-* diff --git a/TODO.md b/TODO.md index 1a22b5d..6b54871 100644 --- a/TODO.md +++ b/TODO.md @@ -21,6 +21,12 @@ fmt-check, and commit. # Completed Steps +- 2026-09-08: Closed the secret exposure in the canonical `.dockerignore`: a + local `.env`, `*.pem` or `*.key` was reaching the build context under + `COPY . .`, invisible to every git-based check. The patterns are now written + to `.dockerignore`'s own semantics — `**/`-prefixed so they hold at every + depth, case-folded with character ranges — and `REPO_POLICIES.md` requires + verifying by enumerating the image rather than by reading the file. - 2026-09-08: Made a pinned tool in `script/bootstrap` actually reach the host. `REPO_POLICIES.md` now requires comparing the installed version against the pin rather than testing `PATH` presence, and re-resolving the binary through diff --git a/prompts/EXISTING_REPO_CHECKLIST.md b/prompts/EXISTING_REPO_CHECKLIST.md index 037cda7..1d5ab1c 100644 --- a/prompts/EXISTING_REPO_CHECKLIST.md +++ b/prompts/EXISTING_REPO_CHECKLIST.md @@ -33,6 +33,15 @@ with your task. build step, and `script/cibuild` and `script/docker` build it with `--no-cache` — fetch `.dockerignore` from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` +- [ ] Every depth-independent pattern in `.dockerignore` carries a `**/` prefix, + only genuinely root-anchored entries such as `.git` are unprefixed, and + `.gitignore`'s patterns have not been transplanted unmodified — the + transplanted form leaves `config/.env` and `certs/server.key` in the build + context while reading as solved +- [ ] `.dockerignore` excludes the repo's own host-built artifacts (compiled + binaries, test binaries, coverage output), written root-anchored — + `/myapp`, never `**/myapp`. An existing repo is where such a binary is + likeliest to already be sitting in the build context, invisible to git. - [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on push — reference `https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml` diff --git a/prompts/NEW_REPO_CHECKLIST.md b/prompts/NEW_REPO_CHECKLIST.md index 6f3dc8b..df92f15 100644 --- a/prompts/NEW_REPO_CHECKLIST.md +++ b/prompts/NEW_REPO_CHECKLIST.md @@ -52,6 +52,13 @@ Template files can be fetched from: `https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md` - [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` + - Extend `.dockerignore` with the repo's own host-built artifacts, giving + every depth-independent pattern a `**/` prefix — but write a repo-root + binary anchored, `/myapp` and never `**/myapp`, which would also match + `cmd/myapp/` and delete the package directory. Do not transplant + `.gitignore`'s patterns: `.dockerignore` anchors an unprefixed pattern at + the context root, so the copied form leaves `config/.env` in the build + context while reading as solved. - All Dockerfiles must run `make check` as a build step - Server: also builds and runs the application - Non-server: brings up dev environment and runs `make check` @@ -108,7 +115,8 @@ are thin shims calling them. Model scripts: - [ ] `make docker` succeeds - [ ] `script/cibuild` succeeds and demonstrably executed the checks — a sub-second build, or `CACHED` on a check layer, means nothing ran -- [ ] No secrets in repo +- [ ] No secrets in repo, and none in the build context: enumerate a probe image + rather than reading `.dockerignore` - [ ] No mutable image/package references - [ ] No unnecessary files in repo root - [ ] All dates written as YYYY-MM-DD diff --git a/prompts/REPO_POLICIES.md b/prompts/REPO_POLICIES.md index e4f3134..061dd1c 100644 --- a/prompts/REPO_POLICIES.md +++ b/prompts/REPO_POLICIES.md @@ -257,7 +257,38 @@ style conventions are in separate documents: editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`. Fetch the standard `.gitignore` from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up - a new repo. + a new repo. These patterns are written to `.gitignore`'s own semantics, in + which an unanchored pattern already matches at every depth; they are not a + `.dockerignore` and must not be transplanted into one unmodified. + +- **`.dockerignore` does not use `.gitignore` semantics, and copying patterns + across unmodified leaves secrets in the build context.** Docker matches with + `moby/patternmatcher`: `filepath.Match` semantics plus a `**` extension, so + `*` does not cross `/` and a pattern without a leading `**/` is anchored at + the build-context root. A `.dockerignore` listing `.env`, `*.pem` and `*.key` + therefore excludes only the copies at the repository root, while `config/.env` + and `certs/server.key` still reach the context and can land in an image layer + — which is more dangerous than a short file with no secret patterns at all, + because it reads as solved and stops anyone looking. Give every + depth-independent pattern the `**/` prefix and leave only genuinely + root-anchored entries unprefixed: `.git`, and the repo's own host-built + binary, written `/myapp` and never `**/myapp`, which would also match + `cmd/myapp/` and delete the package directory from the context. Matching is + case-sensitive, and an ALL-CAPS twin per pattern still misses `Server.Key`, so + secret names use character ranges — `**/*.[kK][eE][yY]`, `**/*.[pP][eE][mM]`, + and likewise for `.envrc` and the extensionless SSH keys. Where such a pattern + also catches something the build needs, re-include it with a negation + (`!docs/example.env`); deleting the pattern reopens the exposure for every + other file it covers. Fetch the standard `.dockerignore` from + `https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` and extend + it with the repo's own artifacts. + +- **Verify `.dockerignore` by enumerating the image, not by reading the + patterns.** Plant files at the root _and_ at least two directories deep, build + a probe image that does `COPY . .`, and list what actually landed + (`docker run --rm --entrypoint find IMAGE /app`). The `transferring context` + size is not a substitute: a nested secret is a few bytes, and BuildKit + transfers only the delta from the previous build. - **No build artifacts in version control.** Code-derived data (compiled bundles, minified output, generated assets) must never be committed to the