Compare commits
1 Commits
533fc61817
...
fd78aeb003
| Author | SHA1 | Date | |
|---|---|---|---|
| fd78aeb003 |
@@ -1,3 +1,70 @@
|
|||||||
|
# Docker matches this file with moby/patternmatcher: Go filepath.Match
|
||||||
|
# semantics plus a `**` extension, compiled to a regexp. Plain
|
||||||
|
# filepath.Match has no `**` at all. What follows from that: `*` does not
|
||||||
|
# cross `/`, and a pattern without a leading `**/` is anchored at the
|
||||||
|
# build-context root. Every depth-independent pattern therefore needs the
|
||||||
|
# `**/` prefix — without it `config/.env` and `certs/server.key` still
|
||||||
|
# ship while the file reads as solved.
|
||||||
|
#
|
||||||
|
# Root-anchored entries are for paths that occur exactly once, at the
|
||||||
|
# context root. A host-built binary is the usual case, and it must be
|
||||||
|
# written anchored: `/myapp`, never `**/myapp`. The prefixed form also
|
||||||
|
# matches `cmd/myapp/`, which deletes the package directory from the
|
||||||
|
# context.
|
||||||
|
#
|
||||||
|
# Matching is case-sensitive, so `**/*.key` does not match
|
||||||
|
# `certs/SERVER.KEY`, which is reachable on the case-insensitive
|
||||||
|
# filesystems most laptops use. Adding an ALL-CAPS twin per pattern is
|
||||||
|
# not the fix: it still misses `Server.Key` while reading as though case
|
||||||
|
# were handled. Character ranges cover every spelling in one line, so
|
||||||
|
# every secret name below is written that way — including the
|
||||||
|
# extensionless SSH keys and `.envrc`, because on those same
|
||||||
|
# case-insensitive filesystems direnv reads `.ENVRC` and ssh reads
|
||||||
|
# `ID_RSA`.
|
||||||
|
#
|
||||||
|
# `**/*.[eE][nN][vV]` also excludes a committed env template such as
|
||||||
|
# `example.env`. If the build genuinely needs one, re-include it with a
|
||||||
|
# negation after the pattern: `!docs/example.env`.
|
||||||
|
#
|
||||||
|
# Extend this file with the repo's own host-built artifacts (compiled
|
||||||
|
# binaries, test binaries, coverage output); those are per-repo and
|
||||||
|
# belong here because a host build otherwise drops them into the
|
||||||
|
# context.
|
||||||
|
|
||||||
|
# Repository metadata: exactly one, at the context root.
|
||||||
.git
|
.git
|
||||||
node_modules
|
|
||||||
.DS_Store
|
# Environment files. `*.env` covers both the bare `.env` name (`*` matches
|
||||||
|
# the empty string) and the `prod.env` convention.
|
||||||
|
**/*.[eE][nN][vV]
|
||||||
|
**/.[eE][nN][vV].*
|
||||||
|
**/.[eE][nN][vV][rR][cC]
|
||||||
|
|
||||||
|
# Private keys and the bundles that carry them. Public certificates
|
||||||
|
# (*.crt, *.cer) are deliberately absent: they are not secrets and are
|
||||||
|
# sometimes a legitimate build input.
|
||||||
|
**/*.[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 under a developer's
|
||||||
|
# hands, so it invalidates COPY for reasons unrelated to the source.
|
||||||
|
**/*.swp
|
||||||
|
**/*.swo
|
||||||
|
**/*~
|
||||||
|
**/*.bak
|
||||||
|
**/.idea
|
||||||
|
**/.vscode
|
||||||
|
**/*.sublime-*
|
||||||
|
|||||||
15
TODO.md
15
TODO.md
@@ -21,6 +21,21 @@ fmt-check, and commit.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-08-09: Closed the secret exposure in the canonical `.dockerignore`: a
|
||||||
|
developer's local `.env`, `*.pem` or `*.key` was reaching the Docker build
|
||||||
|
context under `COPY . .`, invisible to every git-based check because
|
||||||
|
`.gitignore` covers it. The patterns are written to `.dockerignore`'s own
|
||||||
|
`moby/patternmatcher` semantics — `**/`-prefixed so they hold at every depth,
|
||||||
|
which also fixes nested `node_modules` — rather than transplanted from
|
||||||
|
`.gitignore`, whose unprefixed form protects only the repository root while
|
||||||
|
reading as solved. Coverage extends past the `.env`/`.pem`/`.key` trio to the
|
||||||
|
`prod.env` convention, `.envrc`, PKCS#12 bundles and extensionless SSH keys,
|
||||||
|
every one of them case-folded with character ranges because matching is
|
||||||
|
case-sensitive and an ALL-CAPS twin per pattern still misses `Server.Key`.
|
||||||
|
`REPO_POLICIES.md` and both repo checklists now state that asymmetry and
|
||||||
|
require verification by enumerating the image rather than by reading the
|
||||||
|
patterns. Verified with a probe image before, against three naive forms
|
||||||
|
(unprefixed, lowercase-only, ALL-CAPS-doubled), and after.
|
||||||
- 2026-08-09: Made the pinned golangci-lint actually propagate: REPO_POLICIES.md
|
- 2026-08-09: Made the pinned golangci-lint actually propagate: REPO_POLICIES.md
|
||||||
now carries the canonical `script/bootstrap` snippet for Go repos, which
|
now carries the canonical `script/bootstrap` snippet for Go repos, which
|
||||||
installs when the installed version does not match the pin (the old
|
installs when the installed version does not match the pin (the old
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ with your task.
|
|||||||
`CHECK_EPOCH` rule in `REPO_POLICIES.md`. Without them the check layer is
|
`CHECK_EPOCH` rule in `REPO_POLICIES.md`. Without them the check layer is
|
||||||
served from cache on an unchanged tree and the build reports a green it
|
served from cache on an unchanged tree and the build reports a green it
|
||||||
never ran.
|
never ran.
|
||||||
|
- [ ] `.dockerignore` excludes the repo's own host-built artifacts (compiled
|
||||||
|
binaries, test binaries, coverage output), written root-anchored —
|
||||||
|
`/myapp`, never `**/myapp`, which would also match `cmd/myapp/`. An
|
||||||
|
existing repo is where such a binary is likeliest to already be sitting in
|
||||||
|
the build context, invisible to git.
|
||||||
|
- [ ] 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.
|
||||||
|
`.dockerignore` anchors an unprefixed pattern at the context root, so the
|
||||||
|
transplanted form leaves `config/.env` and `certs/server.key` in the build
|
||||||
|
context while reading as solved — see the `.dockerignore` rule in
|
||||||
|
`REPO_POLICIES.md`.
|
||||||
- [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on
|
- [ ] Gitea Actions workflow in `.gitea/workflows/` runs `script/cibuild` on
|
||||||
push — reference
|
push — reference
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ Template files can be fetched from:
|
|||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
|
||||||
- [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from
|
- [ ] `Dockerfile` and `.dockerignore` — fetch `.dockerignore` from
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore`
|
`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. See the `.dockerignore` rule in
|
||||||
|
`REPO_POLICIES.md`.
|
||||||
- All Dockerfiles must run `make check` as a build step, and every stage
|
- All Dockerfiles must run `make check` as a build step, and every stage
|
||||||
containing a check-running `RUN` must declare `ARG CHECK_EPOCH` with the
|
containing a check-running `RUN` must declare `ARG CHECK_EPOCH` with the
|
||||||
`RUN [ -n "$CHECK_EPOCH" ] || exit 1` guard immediately below it — see the
|
`RUN [ -n "$CHECK_EPOCH" ] || exit 1` guard immediately below it — see the
|
||||||
|
|||||||
@@ -331,7 +331,64 @@ style conventions are in separate documents:
|
|||||||
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
|
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
|
||||||
Fetch the standard `.gitignore` from
|
Fetch the standard `.gitignore` from
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
`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 — see the
|
||||||
|
next rule.
|
||||||
|
|
||||||
|
- **`.dockerignore` does not use `.gitignore` semantics, and copying patterns
|
||||||
|
across unmodified leaves secrets in the build context.** Docker matches with
|
||||||
|
`moby/patternmatcher`: Go `filepath.Match` semantics plus a `**` extension,
|
||||||
|
compiled to a regexp — plain `filepath.Match` has no `**` at all. 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; `config/.env` and
|
||||||
|
`certs/server.key` still reach the context and can land in an image layer.
|
||||||
|
That file is more dangerous than a short one with no secret patterns at all,
|
||||||
|
because it reads as solved and stops anyone looking. Give every
|
||||||
|
depth-independent pattern the `**/` prefix — `**/node_modules`,
|
||||||
|
`**/.DS_Store`, and the secret patterns in the canonical file, which are
|
||||||
|
additionally case-folded per the rule below — and leave only genuinely
|
||||||
|
root-anchored entries such as `.git` unprefixed. The inverse move is equally
|
||||||
|
wrong: never apply `**/` to `.gitignore`, where it is redundant and produces a
|
||||||
|
file that is wrong in a way that looks careful. Each file is written to its
|
||||||
|
own semantics; neither is derived from the other. Fetch the standard
|
||||||
|
`.dockerignore` from
|
||||||
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore` and extend
|
||||||
|
it with the repo's own host-built artifacts — a host `make build` that leaves
|
||||||
|
a compiled binary in the repo root puts that binary in the build context,
|
||||||
|
where `.gitignore` hides it from every git-based check. Write that binary
|
||||||
|
anchored, `/myapp` and never `**/myapp`: the prefixed form also matches
|
||||||
|
`cmd/myapp/` and deletes the package directory from the context.
|
||||||
|
|
||||||
|
- **`.dockerignore` matching is case-sensitive, so cover capitalisation with
|
||||||
|
character classes rather than by doubling patterns.** `**/*.key` does not
|
||||||
|
match `certs/SERVER.KEY`, which is reachable on the case-insensitive
|
||||||
|
filesystems most laptops use. Adding an ALL-CAPS twin for each pattern is not
|
||||||
|
the fix: it still misses `Server.Key` and `Ca.Pem` while reading as though
|
||||||
|
case were handled — the same manufactured confidence as the root-anchored
|
||||||
|
form. The matcher supports character ranges, so one line covers every
|
||||||
|
spelling: `**/*.[kK][eE][yY]`, `**/*.[pP][eE][mM]`. Apply this to every secret
|
||||||
|
name, not only to extensions: the extensionless SSH keys and `.envrc` need it
|
||||||
|
for the same reason, since on the very filesystems that make `SERVER.KEY`
|
||||||
|
reachable, direnv reads `.ENVRC` and ssh reads `ID_RSA`. Note that `*` matches
|
||||||
|
the empty string, so `**/*.[eE][nN][vV]` already covers a bare `.ENV` and no
|
||||||
|
separate literal `.env` entry is needed.
|
||||||
|
|
||||||
|
- **A pattern that also catches something the build needs is re-included with a
|
||||||
|
negation, not deleted.** The canonical `**/*.[eE][nN][vV]` excludes a
|
||||||
|
committed env template such as `example.env`; a repo whose build genuinely
|
||||||
|
reads one adds `!docs/example.env` after the pattern. Deleting the pattern
|
||||||
|
instead reopens the exposure for every other file it covers.
|
||||||
|
|
||||||
|
- **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`). Reading the patterns and
|
||||||
|
agreeing they look right is exactly what lets the root-only form through. 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, so the
|
||||||
|
reported size describes the transfer and not the contents of the image.
|
||||||
|
|
||||||
- **No build artifacts in version control.** Code-derived data (compiled
|
- **No build artifacts in version control.** Code-derived data (compiled
|
||||||
bundles, minified output, generated assets) must never be committed to the
|
bundles, minified output, generated assets) must never be committed to the
|
||||||
|
|||||||
Reference in New Issue
Block a user