fix: make .dockerignore exclusions apply at any depth (E00-S02-T08)
CI / Frozen lockfile install (pull_request) Successful in 52s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 36s

Resolve the security review of #389 (findings 1-4):

- .dockerignore: every env/credential pattern is now **/-prefixed
  (**/.env, **/.env.*, **/node_modules, **/.npmrc, ..., **/secrets,
  **/*.pem, **/*.key, ...) and the redundant 'secrets/' line is dropped.
  Docker's matcher (moby/patternmatcher) anchors slash-less patterns to
  the context root, so the bare forms excluded nothing under apps/server/;
  **/ matches the root AND any nested depth. (finding 1, 3)
- tests/secrets-not-embedded.test.mjs: the dockerignore matcher is now a
  faithful port of moby/patternmatcher (filepath.Clean + anchored full-path
  match + parent-directory propagation), not gitignore basename semantics;
  asserts nested example paths (apps/server/.npmrc, config/server.key,
  apps/server/secrets/...) are excluded; requires no redundant equivalent
  patterns verbatim; adds mutation probes for bare-pattern and
  duplicate-pattern regressions. (finding 2, 3)
- apps/server/Dockerfile + compose.yaml: guarantee restated precisely
  (credential files excluded at the context root AND at any depth).
- .gitea/workflows/ci.yml: new job runs
  'node --test tests/secrets-not-embedded.test.mjs' on every PR; the
  docker-gated layer-scan probe runs where a daemon exists, skips cleanly
  otherwise. (finding 4)
- tests/compose-config.test.mjs: .dockerignore presence list updated to the
  **/-prefixed forms (node_modules, .env).

Tested: secrets suite 16 tests -> 15 pass / 1 docker-gated skip / 0 fail;
full suite 101 pass / 12 fail / 8 skip, failures identical to clean main
(env-dependent pnpm/Node-24 suites); matcher port verified against the
moby/patternmatcher evidence table.
This commit is contained in:
implementer
2026-08-29 01:49:07 +00:00
parent b3ad55efe8
commit f00c13d57a
6 changed files with 394 additions and 116 deletions
+6 -4
View File
@@ -21,10 +21,12 @@
# ARG/ENV instruction (the only ENV is `NODE_ENV=production`) and every COPY
# copies a fixed, non-secret path (manifests, source, compiled dist) — never
# `.env` or credential files; `.dockerignore` additionally excludes env and
# credential files from the build context, so a local secret file cannot be
# embedded even by mistake. Runtime credentials (e.g. DATABASE_URL) are
# injected by Compose at run time (compose.yaml `app.environment`), never baked
# into the image. Tests: tests/secrets-not-embedded.test.mjs.
# credential files from the build context at the context root AND at any
# nested depth (its patterns are `**/`-prefixed because Docker's matcher
# anchors slash-less patterns to the context root), so a local secret file
# cannot be embedded even by mistake. Runtime credentials (e.g. DATABASE_URL)
# are injected by Compose at run time (compose.yaml `app.environment`), never
# baked into the image. Tests: tests/secrets-not-embedded.test.mjs.
#
# Image base: node:24.19.0-bookworm-slim (glibc Debian) per Technology-Stack
# §5.4 — argon2 is a native dependency and musl/Alpine causes native-module