Files
prompts/prompts/EXISTING_REPO_CHECKLIST.md
sneak b8d21d1592
All checks were successful
check / check (push) Successful in 8s
Keep secrets out of the Docker build context at every depth (closes #29)
The canonical .dockerignore was three lines -- .git, node_modules, .DS_Store
-- while the canonical Dockerfile does `COPY . .`, so a developer's local
.env, *.pem or *.key was shipped into the build context and could land in an
image layer. Nothing surfaced it because .gitignore covers those patterns, so
the files are invisible to every git-based check.

The obvious repair, copying .gitignore's secret patterns across, is worse than
the gap it closes. .dockerignore does not use .gitignore semantics: Docker
matches with Go filepath.Match, `*` does not cross `/`, and a pattern without
a leading `**/` is anchored at the build-context root. A file listing .env,
*.pem and *.key therefore reads as solved, reviews as solved, and protects
only the repository root, while config/.env and certs/server.key still ship.
The three-line file at least invited scrutiny; the transplanted form
manufactures confidence and stops anyone looking.

So every depth-independent pattern here carries the `**/` prefix and only
genuinely root-anchored entries stay unprefixed. `**/node_modules` fixes a
defect the three-line file had today for any nested node_modules,
independently of the secret exposure.

The OS and editor patterns are included on their own merits rather than by
mirroring .gitignore. None of them is ever a build input, and editor state in
particular churns under a developer's hands, so each one is a source of
`COPY . .` invalidation carrying no information about the source tree. Now
that the checks are keyed on CHECK_EPOCH rather than on accidental context
churn, there is no reason left to keep churn in the context. Language build
artifacts are deliberately absent: they are per-repo, and the file's header
comment tells consuming repos to add their own host-built binaries, which is
the case that actually bites -- a host `make build` drops a multi-megabyte
artifact into the context where .gitignore hides it from every git-based
check.

.gitignore is untouched. Its semantics are the inverse: an unanchored pattern
already matches at any depth, so `**/`-prefixing it produces a file that is
wrong in a way that looks careful. That asymmetry is why "derive one from the
other" was the wrong instruction, and it is now written down in
REPO_POLICIES.md in both directions, together with the requirement to verify
by enumerating the image rather than by reading the patterns. Every consuming
repo inherits .dockerignore by copy, so the trap has to live where the next
person looks, not only be fixed once here. Both repo checklists gain the same
requirement, since they are what an agent reads while extending the file.

Verified by planting .env, server.key and ca.pem at the root plus config/.env,
config/.env.production, certs/ca.pem, certs/server.key,
deploy/secrets/id_rsa.key, web/node_modules/nested/index.js and a nested .swp
below it, then building a standalone probe image doing `COPY . .` and listing
what actually landed inside it. Before: all eleven planted files in the image.
Against the naive unprefixed form: the three root-level files excluded and
every nested one still present, which is what shows the enumeration can detect
the failure mode at all. After: every planted file excluded at every depth,
with web/src/app.js still present to prove the probe was copying nested files
rather than copying nothing.

Transferred-context size is recorded but load-bearing on nothing, and the runs
show why: the naive build reported 2.18kB transferred while 43 files, five of
them secrets, were in the image. BuildKit transfers only the delta from the
previous build, so the number describes the transfer and not the contents.

Planted files were removed and their absence confirmed against the filesystem
rather than against `git status`, which could not have seen them.

`make docker` re-run after the change: the check layer executed rather than
being served from cache, so the CHECK_EPOCH verification still holds under the
altered build context.
2026-08-09 16:10:45 +00:00

5.7 KiB

title, last_modified
title last_modified
Existing Repo Checklist 2026-08-09

Use this checklist when beginning work in a repo that may not yet conform to our repository policies (https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md).

Work on a feature branch. Check each item and fix any gaps before proceeding with your task.

Formatting (do this first)

  • If the repo has never been formatted to our standards, run make fmt and commit the result as a standalone branch/commit/PR before any other changes. Formatting diffs can be large and should not be mixed with functional changes.

Required Files

  • README.md exists with all required sections (Description, Getting Started, Rationale, Design, TODO, License, Author)
  • LICENSE file exists and matches the README
  • REPO_POLICIES.md exists and version date is current — fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md
  • .gitignore is comprehensive (OS, editor, language artifacts, secrets) — fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore if missing
  • .editorconfig exists — fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig
  • Dockerfile and .dockerignore exist (fetch .dockerignore from https://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore); Dockerfile runs make check as a build step, and every stage containing a check-running RUN declares ARG CHECK_EPOCH with the RUN [ -n "$CHECK_EPOCH" ] || exit 1 guard immediately below it — see the 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 never ran.
  • 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 push — reference https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml
  • Language-specific config:
    • Go: go.mod, go.sum, .golangci.yml (fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml)
    • JS: package.json, yarn.lock, .prettierrc, .prettierignore (fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrc and https://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore)
    • Python: pyproject.toml
    • Docs/writing: .prettierrc, .prettierignore (same URLs as above)

Makefile and script/ Entrypoints

  • Makefile exists in root — reference https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile
  • Has targets: test, lint, fmt, fmt-check, check, docker, hooks
  • Target implementations live in script/ (scripts-to-rule-them-all); Makefile targets are thin shims calling them — model scripts at https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>
  • script/precommit exists and the pre-commit hook (installed by script/install-precommit, shimmed by make hooks) runs it
  • README has an Entrypoints section documenting the script/ entrypoints and linking the standard
  • make check does not modify any files in the repo
  • make test has a 30-second timeout
  • make test runs real tests, not a no-op (at minimum, import/compile check)
  • make check passes on current branch

Formatting

  • Platform-standard formatter is configured (black, prettier, go fmt)
  • Default formatter config, only exception: four-space indents (except Go)
  • All files pass make fmt-check

Git Hygiene

  • Pre-commit hook is installed (make hooks)
  • No secrets in the repo (.env, keys, credentials)
  • No mutable references in Dockerfiles or scripts (tags, @latest) — all pinned by cryptographic hash with version/date comment
  • Using yarn, not npm (JS projects)

Directory Structure

  • No unnecessary files in repo root
  • Files organized into canonical subdirectories (bin/, cmd/, docs/, internal/, static/, etc.)
  • Go migrations in internal/db/migrations/ and embedded in binary

HTTP Service Hardening (if targeting 1.0 and the repo is an HTTP/web service)

  • Security headers set on all responses (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
  • Request body size limits enforced on all endpoints
  • Read/write/idle timeouts configured on the HTTP server (slowloris defense)
  • Per-handler execution time limits in place
  • Password-based auth endpoints are rate-limited
  • CSRF tokens on all state-mutating HTML forms
  • Passwords hashed with bcrypt, scrypt, or argon2
  • Session cookies use HttpOnly, Secure, and SameSite attributes
  • True client IP correctly detected behind reverse proxy (trusted proxy allowlist configured)
  • CORS restricted to explicit origin allowlist for authenticated endpoints
  • Error responses do not leak stack traces, SQL queries, or internal paths

Final

  • make check passes
  • script/cibuild succeeds (a bare docker build . fails closed by design, on the CHECK_EPOCH guard)
  • Commit and merge fixes before starting your actual task