[E00-S04-T05] .env.example contains placeholders only #404

Merged
kpcto merged 6 commits from feature/186 into main 2026-08-30 05:47:44 +00:00
Member

What changed

  • .env.example (new, repo root) — the committed configuration template ([E00-S04-T05]): documents every configuration environment source read by the config package (HOST/PORT/DATABASE_URL/EPPP_SESSION_SECRET) plus the optional Docker Compose override variables (POSTGRES_DB/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_PORT/APP_PORT), with placeholder values only — no real secret values.
  • .gitignore — real .env/.env.* files stay ignored; the committed .env.example is explicitly un-ignored (!.env.example) so the template stays tracked.
  • Tests — tests/env-example.test.mjs (new, dependency-free node:test): locks in every acceptance criterion with mutation probes proving the assertions are non-vacuous.
  • CI — new env-example job (E00-S04-T05) gates the suite on every PR. ⚠️ Pipeline tripwire: any change to .gitea/workflows/ requires a human maintainer decision/approval on this PR (security finding F1 — issue #186 Notes; no code change required beyond that sign-off).
  • Docs/comments — non-container guide, config package boundary/description, and compose.yaml now point at the committed template instead of deferring it to a later task.

Security review findings (PR #404 round 1, request-changes) — addressed

Finding Severity Resolution in this revision
F1 — CI workflow change (pipeline tripwire) blocker Recorded in issue #186 Notes; human-decision sign-off required on this PR. No code change.
F2 — gitleaks hit on secret-shaped mutation-probe literal should Probe value now built at runtime from short non-secret fragments; gitleaks detect --source . --no-git --redact → 0 hits
F3 — EPPP_SESSION_SECRET placeholder 42 chars (fails open) should Placeholder shortened to change-me (9 chars < the schema's 32-char minimum) so an unedited cp .env.example .env fails startup validation (fails closed)
F4 — assertion messages interpolate raw values nit New maskValue() helper; every message that echoes a template value masks/truncates it (never the raw string)

Criterion → test mapping

Criterion (issue #186) Test
.env.example contains placeholders only env-example: "a committed .env.example exists at the repo root"; ".env.example contains placeholders only and no real secret values" — every assignment value must be a benign non-secret default (0.0.0.0, ports, localhost, eppp, the no-credential local DATABASE_URL) or carry an explicit placeholder marker (change-me/<…>); every line is a comment, a blank line, or a well-formed KEY=value; no duplicate variables; the file documents every config-schema environment source (HOST/PORT/DATABASE_URL/EPPP_SESSION_SECRET). Mutation probes: a long secret-looking value, a credential-URI value, a missing required variable, and a malformed line all fail
no real secret values appear in the example file env-example: the same value predicate rejects secret-shaped values, and the compose dev-default credential (postgres://eppp:eppp@db:5432/eppp) is rejected anywhere in the file — values or comments. Mutation probes: injecting the compose default credential or postgres://alice:supersecret@… fails. Supporting: ".gitignore keeps real .env files ignored while un-ignoring the committed example" (+ mutation probe dropping !.env.example fails)
EPPP_SESSION_SECRET fails closed: its .env.example placeholder is shorter than the schema's 32-character minimum env-example: "EPPP_SESSION_SECRET's placeholder is shorter than the schema's 32-character minimum (fails closed)" — asserts the shipped placeholder length is < 32 chars so an unedited copy is rejected at startup. Mutation probe: replacing it with the 40-char runtime-built probe fails the placeholder-only criterion
the branch stays gitleaks-clean: secret-shaped mutation-probe literals are computed at runtime or carry an inline # gitleaks:allow env-example: the secret-shaped probe (SECRET_SHAPED_PROBE) is built at runtime by joining short non-secret fragments — no secret-shaped literal in the source tree. Verified: gitleaks detect --source . --no-git --redact → 0 hits on the branch
test assertion messages never interpolate a raw secret/placeholder value; a failing assertion echoes a truncated or masked value env-example: maskValue() truncates any template-derived value echoed in an assertion message (placeholder values, forbidden credential values, malformed lines); messages echo e.g. got: "aB3d...<40 chars>", never the raw string

Risks

  • No runtime code paths change; the new test is node:test-only (no dependencies, lockfile untouched).
  • .env.example is already excluded from the Docker build context by the existing **/.env.* dockerignore pattern (covered by secrets-not-embedded), so the template cannot enter an image.
  • The CI workflow addition is a pipeline tripwire (review checklist §6.4): a human maintainer must approve the .gitea/workflows/ change on this PR (issue #186 Notes, finding F1).
  • Out of scope per issue: process.env access rule (E00-S04-T04) and TypeBox/Ajv schema (E00-S04-T01).
## What changed - **`.env.example`** (new, repo root) — the committed configuration template ([E00-S04-T05]): documents every configuration environment source read by the config package (`HOST`/`PORT`/`DATABASE_URL`/`EPPP_SESSION_SECRET`) plus the optional Docker Compose override variables (`POSTGRES_DB`/`POSTGRES_USER`/`POSTGRES_PASSWORD`/`POSTGRES_PORT`/`APP_PORT`), with **placeholder values only** — no real secret values. - **`.gitignore`** — real `.env`/`.env.*` files stay ignored; the committed `.env.example` is explicitly un-ignored (`!.env.example`) so the template stays tracked. - **Tests** — `tests/env-example.test.mjs` (new, dependency-free `node:test`): locks in every acceptance criterion with mutation probes proving the assertions are non-vacuous. - **CI** — new `env-example` job (E00-S04-T05) gates the suite on every PR. ⚠️ Pipeline tripwire: any change to `.gitea/workflows/` requires a human maintainer decision/approval on this PR (security finding F1 — issue #186 Notes; no code change required beyond that sign-off). - **Docs/comments** — non-container guide, config package boundary/description, and `compose.yaml` now point at the committed template instead of deferring it to a later task. ### Security review findings (PR #404 round 1, request-changes) — addressed | Finding | Severity | Resolution in this revision | | --- | --- | --- | | F1 — CI workflow change (pipeline tripwire) | blocker | Recorded in issue #186 Notes; human-decision sign-off required on this PR. No code change. | | F2 — gitleaks hit on secret-shaped mutation-probe literal | should | Probe value now built at runtime from short non-secret fragments; `gitleaks detect --source . --no-git --redact` → 0 hits | | F3 — `EPPP_SESSION_SECRET` placeholder 42 chars (fails open) | should | Placeholder shortened to `change-me` (9 chars < the schema's 32-char minimum) so an unedited `cp .env.example .env` fails startup validation (fails closed) | | F4 — assertion messages interpolate raw values | nit | New `maskValue()` helper; every message that echoes a template value masks/truncates it (never the raw string) | ## Criterion → test mapping | Criterion (issue #186) | Test | | --- | --- | | `.env.example` contains placeholders only | `env-example`: "a committed .env.example exists at the repo root"; ".env.example contains placeholders only and no real secret values" — every assignment value must be a benign non-secret default (`0.0.0.0`, ports, `localhost`, `eppp`, the no-credential local `DATABASE_URL`) or carry an explicit placeholder marker (`change-me`/`<…>`); every line is a comment, a blank line, or a well-formed `KEY=value`; no duplicate variables; the file documents every config-schema environment source (`HOST`/`PORT`/`DATABASE_URL`/`EPPP_SESSION_SECRET`). Mutation probes: a long secret-looking value, a credential-URI value, a missing required variable, and a malformed line all fail | | no real secret values appear in the example file | `env-example`: the same value predicate rejects secret-shaped values, and the compose dev-default credential (`postgres://eppp:eppp@db:5432/eppp`) is rejected anywhere in the file — values or comments. Mutation probes: injecting the compose default credential or `postgres://alice:supersecret@…` fails. Supporting: ".gitignore keeps real .env files ignored while un-ignoring the committed example" (+ mutation probe dropping `!.env.example` fails) | | `EPPP_SESSION_SECRET` fails closed: its `.env.example` placeholder is shorter than the schema's 32-character minimum | `env-example`: "EPPP_SESSION_SECRET's placeholder is shorter than the schema's 32-character minimum (fails closed)" — asserts the shipped placeholder length is < 32 chars so an unedited copy is rejected at startup. Mutation probe: replacing it with the 40-char runtime-built probe fails the placeholder-only criterion | | the branch stays gitleaks-clean: secret-shaped mutation-probe literals are computed at runtime or carry an inline `# gitleaks:allow` | `env-example`: the secret-shaped probe (`SECRET_SHAPED_PROBE`) is built at runtime by joining short non-secret fragments — no secret-shaped literal in the source tree. Verified: `gitleaks detect --source . --no-git --redact` → 0 hits on the branch | | test assertion messages never interpolate a raw secret/placeholder value; a failing assertion echoes a truncated or masked value | `env-example`: `maskValue()` truncates any template-derived value echoed in an assertion message (placeholder values, forbidden credential values, malformed lines); messages echo e.g. `got: "aB3d...<40 chars>"`, never the raw string | ## Risks - No runtime code paths change; the new test is `node:test`-only (no dependencies, lockfile untouched). - `.env.example` is already excluded from the Docker build context by the existing `**/.env.*` dockerignore pattern (covered by `secrets-not-embedded`), so the template cannot enter an image. - The CI workflow addition is a pipeline tripwire (review checklist §6.4): a human maintainer must approve the `.gitea/workflows/` change on this PR (issue #186 Notes, finding F1). - Out of scope per issue: process.env access rule (E00-S04-T04) and TypeBox/Ajv schema (E00-S04-T01).
bot-implementer added 5 commits 2026-08-30 05:12:32 +00:00
docs: point at the committed .env.example template (E00-S04-T05)
CI / Frozen lockfile install (pull_request) Successful in 45s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 25s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 26s
CI / Migration ledger (E00-S03-T03) (pull_request) Successful in 43s
CI / Migration advisory lock (E00-S03-T04) (pull_request) Successful in 42s
CI / Migration failure diagnostic (E00-S03-T05) (pull_request) Successful in 49s
CI / App readiness after migrations (E00-S03-T06) (pull_request) Successful in 1m3s
CI / Field-specific startup errors (E00-S04-T02) (pull_request) Successful in 1m12s
CI / Secret redaction from logs (E00-S04-T03) (pull_request) Successful in 1m6s
CI / Env adapter owns process.env (E00-S04-T04) (pull_request) Successful in 1m8s
CI / TypeBox/Ajv config schema (E00-S04-T01) (pull_request) Successful in 52s
CI / .env.example placeholders only (E00-S04-T05) (pull_request) Successful in 25s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 28s
ec4719a8b4
Member
phase: start
agent: tester
issue: 186
pr: 404
branch: feature/186 -> main
head_sha: ec4719a8b4256023353fd2ae25ce1336bd52dd3d
base_sha: 1e0f6286512dc3de849efcada211cc63a27ca59f
objective: independently probe the PR for #186
acceptance:
  - ".env.example contains placeholders only"
  - "no real secret values appear in the example file"
constraints:
  - "never edit the implementer's tests in the same PR"
plan:
  - inspect .env.example content (placeholders vs real secrets)
  - run tests/env-example.test.mjs locally on feature/186
  - verify CI run for head_sha ec4719a8b4256023353fd2ae25ce1336bd52dd3d
  - post evidence with CI links
status: in-progress
```yaml phase: start agent: tester issue: 186 pr: 404 branch: feature/186 -> main head_sha: ec4719a8b4256023353fd2ae25ce1336bd52dd3d base_sha: 1e0f6286512dc3de849efcada211cc63a27ca59f objective: independently probe the PR for #186 acceptance: - ".env.example contains placeholders only" - "no real secret values appear in the example file" constraints: - "never edit the implementer's tests in the same PR" plan: - inspect .env.example content (placeholders vs real secrets) - run tests/env-example.test.mjs locally on feature/186 - verify CI run for head_sha ec4719a8b4256023353fd2ae25ce1336bd52dd3d - post evidence with CI links status: in-progress ```
bot-implementer added 1 commit 2026-08-30 05:27:20 +00:00
fix: resolve security review findings on .env.example template (E00-S04-T05)
CI / Frozen lockfile install (pull_request) Successful in 53s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 25s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 24s
CI / Migration ledger (E00-S03-T03) (pull_request) Successful in 52s
CI / Migration advisory lock (E00-S03-T04) (pull_request) Successful in 49s
CI / Migration failure diagnostic (E00-S03-T05) (pull_request) Successful in 47s
CI / App readiness after migrations (E00-S03-T06) (pull_request) Successful in 1m8s
CI / Field-specific startup errors (E00-S04-T02) (pull_request) Successful in 1m2s
CI / Secret redaction from logs (E00-S04-T03) (pull_request) Successful in 1m5s
CI / Env adapter owns process.env (E00-S04-T04) (pull_request) Successful in 1m2s
CI / TypeBox/Ajv config schema (E00-S04-T01) (pull_request) Successful in 58s
CI / .env.example placeholders only (E00-S04-T05) (pull_request) Successful in 24s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 25s
f4a282df9e
Addresses PR #404 review findings F2/F3/F4 on issue #186:

- F3 (fail-closed): ship EPPP_SESSION_SECRET placeholder as `change-me`
  (9 chars), shorter than the schema's 32-character minimum, so an
  unedited `cp .env.example .env` is rejected at startup instead of
  booting with a publicly known secret.
- F2 (gitleaks-clean): build the secret-shaped mutation-probe literal at
  runtime from short non-secret fragments; the branch no longer embeds a
  secret-shaped literal in the test source.
- F4 (masked messages): assertion messages mask/truncate values that come
  from the template instead of echoing the raw string.
- test: add a fail-closed length test for the EPPP_SESSION_SECRET
  placeholder (< 32 chars); keep mutation probes non-vacuous.
Member
phase: end
agent: tester
issue: 186
pr: 404
branch: feature/186 -> main
head_sha: ec4719a8b4256023353fd2ae25ce1336bd52dd3d
verdict: probe-complete
summary: "brief's two acceptance criteria PASS; independent probe of issue #186 full criteria surfaced a blocker (F3 fail-closed) plus F2/F4 findings and the F1 CI tripwire"
brief_acceptance:
  - criterion: ".env.example contains placeholders only"
    result: PASS
    evidence: "all 9 assignment values are benign defaults (0.0.0.0, 3000, 5432, localhost, eppp, postgres://localhost:5432/eppp) or explicit change-me placeholders"
  - criterion: "no real secret values appear in the example file"
    result: PASS
    evidence: "independent scan: no user:pass@ credential URI, no long random-looking token, compose dev-default postgres://eppp:eppp@db:5432/eppp absent"
evidence:
  local_test: "node --test tests/env-example.test.mjs -> 9/9 pass, 0 fail (Node v22.23.2)"
  ci_run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/107 -> success, 13/13 jobs green (head_sha ec4719a)"
  ci_job_env_example: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/107/jobs/314 -> .env.example placeholders only (E00-S04-T05) -> success (log: tests 9, pass 9, fail 0)"
findings:
  - id: F3-fail-closed
    severity: blocker
    status: violated
    detail: "issue F3 requires the EPPP_SESSION_SECRET placeholder be SHORTER than 32 chars so an unedited `cp .env.example .env` is rejected at startup; the committed value change-me-to-a-random-32-character-secret is 41 chars, passing schema minLength:32, so the server would boot with a publicly-known secret (config does not reject the change-me marker - out of scope per issue)"
  - id: F2-gitleaks-clean
    severity: should
    status: likely-violated
    detail: "issue F2 requires mutation-probe literals be runtime-computed or carry `# gitleaks:allow`; tests/env-example.test.mjs lines 46/179/192/202 embed raw literals (postgres://eppp:eppp@db:5432/eppp, 43-char token aB3dE9fG0hI1jK2lM3nO4pQ5rS6tU7vW8xY9zA0 after EPPP_SESSION_SECRET=, postgres://alice:supersecret@db.example.com:5432/eppp) with zero `# gitleaks:allow` anywhere in the repo; gitleaks binary not runnable in my sandbox (no curl/wget/go/docker) - reviewer should run `gitleaks detect --source . --no-git --redact`"
  - id: F4-assertion-redaction
    severity: should
    status: violated
    detail: "issue F4 requires assertion messages never interpolate a raw secret/placeholder value; line 124 interpolates ${forbidden} (credential URI) and line 144 interpolates `got: ${value}` (raw value), so a failing assertion would echo the raw secret into CI logs"
  - id: F1-ci-tripwire
    severity: needs-human-decision
    status: flagged
    detail: ".gitea/workflows/ci.yml adds an `env-example` job - pipeline tripwire per review-checklist 6.4 and issue #186 notes; requires human maintainer approval regardless of author"
constraint_compliance: "no files edited, no commits made; implementer tests untouched"
```yaml phase: end agent: tester issue: 186 pr: 404 branch: feature/186 -> main head_sha: ec4719a8b4256023353fd2ae25ce1336bd52dd3d verdict: probe-complete summary: "brief's two acceptance criteria PASS; independent probe of issue #186 full criteria surfaced a blocker (F3 fail-closed) plus F2/F4 findings and the F1 CI tripwire" brief_acceptance: - criterion: ".env.example contains placeholders only" result: PASS evidence: "all 9 assignment values are benign defaults (0.0.0.0, 3000, 5432, localhost, eppp, postgres://localhost:5432/eppp) or explicit change-me placeholders" - criterion: "no real secret values appear in the example file" result: PASS evidence: "independent scan: no user:pass@ credential URI, no long random-looking token, compose dev-default postgres://eppp:eppp@db:5432/eppp absent" evidence: local_test: "node --test tests/env-example.test.mjs -> 9/9 pass, 0 fail (Node v22.23.2)" ci_run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/107 -> success, 13/13 jobs green (head_sha ec4719a)" ci_job_env_example: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/107/jobs/314 -> .env.example placeholders only (E00-S04-T05) -> success (log: tests 9, pass 9, fail 0)" findings: - id: F3-fail-closed severity: blocker status: violated detail: "issue F3 requires the EPPP_SESSION_SECRET placeholder be SHORTER than 32 chars so an unedited `cp .env.example .env` is rejected at startup; the committed value change-me-to-a-random-32-character-secret is 41 chars, passing schema minLength:32, so the server would boot with a publicly-known secret (config does not reject the change-me marker - out of scope per issue)" - id: F2-gitleaks-clean severity: should status: likely-violated detail: "issue F2 requires mutation-probe literals be runtime-computed or carry `# gitleaks:allow`; tests/env-example.test.mjs lines 46/179/192/202 embed raw literals (postgres://eppp:eppp@db:5432/eppp, 43-char token aB3dE9fG0hI1jK2lM3nO4pQ5rS6tU7vW8xY9zA0 after EPPP_SESSION_SECRET=, postgres://alice:supersecret@db.example.com:5432/eppp) with zero `# gitleaks:allow` anywhere in the repo; gitleaks binary not runnable in my sandbox (no curl/wget/go/docker) - reviewer should run `gitleaks detect --source . --no-git --redact`" - id: F4-assertion-redaction severity: should status: violated detail: "issue F4 requires assertion messages never interpolate a raw secret/placeholder value; line 124 interpolates ${forbidden} (credential URI) and line 144 interpolates `got: ${value}` (raw value), so a failing assertion would echo the raw secret into CI logs" - id: F1-ci-tripwire severity: needs-human-decision status: flagged detail: ".gitea/workflows/ci.yml adds an `env-example` job - pipeline tripwire per review-checklist 6.4 and issue #186 notes; requires human maintainer approval regardless of author" constraint_compliance: "no files edited, no commits made; implementer tests untouched" ```
kpcto merged commit dce6cac05b into main 2026-08-30 05:47:44 +00:00
kpcto deleted branch feature/186 2026-08-30 05:47:44 +00:00
Sign in to join this conversation.