docs(16-06): complete image hygiene CI assertions plan
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
---
|
||||
phase: 16-ci-dependency-audit-and-security-checks
|
||||
plan: "06"
|
||||
subsystem: ci
|
||||
tags: [ci, docker, image-hygiene, boot-smoke, security, publish]
|
||||
dependency_graph:
|
||||
requires: ["16-01", "16-04"]
|
||||
provides: ["IMG-03"]
|
||||
affects: [".gitea/workflows/publish.yml"]
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Static assertion step: grep-based structural checks in shell before docker push"
|
||||
- "Boot-smoke: docker run with forbidden env combo + timeout + exit-code semantics"
|
||||
key_files:
|
||||
created: []
|
||||
modified:
|
||||
- .gitea/workflows/publish.yml
|
||||
decisions:
|
||||
- "D-10 (16-06): Static assertions grep for 7 forbidden .dockerignore patterns + --target production pin; grep is substring-safe (apps/api/scripts matches apps/api/scripts/seed-credential.mjs)"
|
||||
- "D-10 (16-06): Boot-smoke treats EXIT==0 and EXIT==124 as failures; any other non-zero is a PASS — covers the D-08 guard's process.exit(1) path"
|
||||
- "D-10 (16-06): Both hygiene steps ordered strictly after build and before push; set -euo pipefail ensures any failure stops the job before push runs"
|
||||
metrics:
|
||||
duration: "1 minute"
|
||||
completed: "2026-06-13"
|
||||
tasks_completed: 2
|
||||
files_modified: 1
|
||||
requirements: [IMG-03]
|
||||
---
|
||||
|
||||
# Phase 16 Plan 06: Image Hygiene CI Assertions Summary
|
||||
|
||||
**One-liner:** Publish-time CI assertions that block docker push when .dockerignore is incomplete, --target production is dropped, or the production image tolerates DEV_AUTH_BYPASS=true (D-10 / IMG-03).
|
||||
|
||||
## What Was Built
|
||||
|
||||
Two CI assertion steps added to `.gitea/workflows/publish.yml`, inserted strictly between the `Build production image` step and the `Push image` step:
|
||||
|
||||
**Step 1 — "Image hygiene — static assertions"** (`set -euo pipefail`):
|
||||
- Fails if `.dockerignore` is absent
|
||||
- Loops over 7 forbidden patterns (`.env`, `node_modules`, `apps/api/scripts`, `.git`, `.planning`, `apps/api/tests`, `apps/pwa/e2e`) and fails if any is missing from `.dockerignore`
|
||||
- Fails if `--target production` is no longer grep-able in `publish.yml` itself
|
||||
- Catches config drift that would ship secrets or dev files (T-16-19)
|
||||
|
||||
**Step 2 — "Image hygiene — boot-smoke (must refuse dev-bypass in production)"** (`set -euo pipefail`):
|
||||
- Runs the freshly-built `sha_tag` image with `NODE_ENV=production DEV_AUTH_BYPASS=true`
|
||||
- `timeout 15` caps the run (T-16-21: prevents the job hanging if the guard does not fire)
|
||||
- EXIT==0 → image started → guard NOT working → FAIL
|
||||
- EXIT==124 → timeout → guard not firing → FAIL
|
||||
- Any other non-zero exit → image refused boot → PASS
|
||||
- Proves `assertNotDevBypassInProduction()` (16-01 / D-08) fires in the actual shipped image (T-16-18)
|
||||
|
||||
The step order in the publish job is now:
|
||||
1. Checkout
|
||||
2. Compute image tags
|
||||
3. Docker login
|
||||
4. Build production image
|
||||
5. Image hygiene — static assertions ← new
|
||||
6. Image hygiene — boot-smoke ← new
|
||||
7. Push image
|
||||
8. Docker logout (if: always())
|
||||
|
||||
## Task Commits
|
||||
|
||||
| Task | Name | Commit | Files |
|
||||
|------|------|--------|-------|
|
||||
| 1 | Split publish build and push into separate steps | bc00f3e | .gitea/workflows/publish.yml |
|
||||
| 2 | Add static image-hygiene assertion + boot-smoke before push | 27046db | .gitea/workflows/publish.yml |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Verification
|
||||
|
||||
- YAML parses cleanly (python3 yaml.safe_load confirmed)
|
||||
- Step ordering verified: assertions at indices 4-5, push at index 6 (between Build at 3 and Push at 6)
|
||||
- All 7 forbidden .dockerignore patterns confirmed present in .dockerignore
|
||||
- `--target production` grep confirmed to match within publish.yml
|
||||
- Dockerfile `ENV NODE_ENV=production` confirmed in production stage (line 45) — precondition for the smoke to be meaningful
|
||||
- `bootGuards.ts` `process.exit(1)` confirmed fires when `NODE_ENV=production AND DEV_AUTH_BYPASS=true` — that is what the smoke exercises
|
||||
|
||||
## Threat Coverage
|
||||
|
||||
| Threat ID | Description | Mitigated By |
|
||||
|-----------|-------------|--------------|
|
||||
| T-16-18 | Regressed image that tolerates DEV_AUTH_BYPASS=true published | Boot-smoke step: fails push if image starts under forbidden combo |
|
||||
| T-16-19 | Future change removes .dockerignore or drops --target production | Static assertions step: fails push on any missing pattern or pin |
|
||||
| T-16-20 | Assertions run after push (bad image already published) | Ordering: both steps are before Push image step |
|
||||
| T-16-21 | Boot-smoke hangs if guard does not fire | `timeout 15`; EXIT==124 treated as failure |
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None.
|
||||
|
||||
## Self-Check
|
||||
|
||||
Files modified:
|
||||
- `.gitea/workflows/publish.yml` — modified (confirmed by git log)
|
||||
|
||||
Commits:
|
||||
- `bc00f3e` — ci(16-06): split publish build and push into separate steps
|
||||
- `27046db` — ci(16-06): add static image-hygiene assertion + boot-smoke before push
|
||||
|
||||
## Self-Check: PASSED
|
||||
Reference in New Issue
Block a user