Phase 16: CI dependency audit & security checks #15
@@ -34,7 +34,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
- [x] **Phase 13: Real Lint Gate (ESLint)** - Wire ESLint flat config (typescript-eslint + React) across both apps so the Phase 8 CI lint slot actually fails on violations instead of no-op'ing (completed 2026-06-12)
|
- [x] **Phase 13: Real Lint Gate (ESLint)** - Wire ESLint flat config (typescript-eslint + React) across both apps so the Phase 8 CI lint slot actually fails on violations instead of no-op'ing (completed 2026-06-12)
|
||||||
- [x] **Phase 14: Desktop E2E Coverage** - Add a Desktop Chrome Playwright profile + make the mobile-authored specs desktop-safe so the Phase 8 regression gate validates desktop, not just mobile (completed 2026-06-12)
|
- [x] **Phase 14: Desktop E2E Coverage** - Add a Desktop Chrome Playwright profile + make the mobile-authored specs desktop-safe so the Phase 8 regression gate validates desktop, not just mobile (completed 2026-06-12)
|
||||||
- [x] **Phase 15: Doc-Only CI Skip + Markdown Lint** - Aggregate-gate the slow api/harness CI jobs so doc-only PRs to main merge without running them (no branch-protection deadlock), and add markdownlint to `fast-checks` so docs get a fast format+lint gate (promoted from backlog 999.17) (completed 2026-06-12)
|
- [x] **Phase 15: Doc-Only CI Skip + Markdown Lint** - Aggregate-gate the slow api/harness CI jobs so doc-only PRs to main merge without running them (no branch-protection deadlock), and add markdownlint to `fast-checks` so docs get a fast format+lint gate (promoted from backlog 999.17) (completed 2026-06-12)
|
||||||
- [ ] **Phase 16: CI Dependency Audit, Security Checks & Image Hygiene** - Extend Gitea CI with outdated-dependency reporting + vulnerability audit + a baseline of additional security checks, and enforce the dev/prod image boundary so no dev-bypass, secret, or family data ships in published images (absorbs backlog 999.17); independent of the admin chain
|
- [x] **Phase 16: CI Dependency Audit, Security Checks & Image Hygiene** - Extend Gitea CI with outdated-dependency reporting + vulnerability audit + a baseline of additional security checks, and enforce the dev/prod image boundary so no dev-bypass, secret, or family data ships in published images (absorbs backlog 999.17); independent of the admin chain (completed 2026-06-13)
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ Plans:
|
|||||||
**Wave 2** *(blocked on Wave 1 completion)*
|
**Wave 2** *(blocked on Wave 1 completion)*
|
||||||
|
|
||||||
- [x] 16-05-PLAN.md — Add the security job to ci.yml (gitleaks always; audit/outdated code-gated) + gate wiring (CI-03)
|
- [x] 16-05-PLAN.md — Add the security job to ci.yml (gitleaks always; audit/outdated code-gated) + gate wiring (CI-03)
|
||||||
- [ ] 16-06-PLAN.md — publish.yml static image-hygiene assertion + boot-smoke before push (IMG-03)
|
- [x] 16-06-PLAN.md — publish.yml static image-hygiene assertion + boot-smoke before push (IMG-03)
|
||||||
|
|
||||||
**UI hint**: no
|
**UI hint**: no
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ Plans:
|
|||||||
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
|
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
|
||||||
| 14. Desktop E2E Coverage | v1.1 | 1/1 | Complete | 2026-06-12 |
|
| 14. Desktop E2E Coverage | v1.1 | 1/1 | Complete | 2026-06-12 |
|
||||||
| 15. Doc-Only CI Skip + MD Lint | v1.1 | 3/3 | Complete | 2026-06-12 |
|
| 15. Doc-Only CI Skip + MD Lint | v1.1 | 3/3 | Complete | 2026-06-12 |
|
||||||
| 16. CI Dep Audit, Sec & Img Hyg | v1.1 | 5/6 | In Progress| |
|
| 16. CI Dep Audit, Sec & Img Hyg | v1.1 | 6/6 | Complete | 2026-06-13 |
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|
||||||
@@ -367,7 +367,7 @@ Plans:
|
|||||||
|
|
||||||
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
||||||
**Requirements:** TBD
|
**Requirements:** TBD
|
||||||
**Plans:** 5/6 plans executed
|
**Plans:** 6/6 plans complete
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -2,16 +2,16 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.1
|
milestone: v1.1
|
||||||
milestone_name: Operability & Polish
|
milestone_name: Operability & Polish
|
||||||
status: executing
|
status: verifying
|
||||||
stopped_at: Completed 16-05-PLAN.md
|
stopped_at: Completed 16-05-PLAN.md
|
||||||
last_updated: "2026-06-13T12:24:49.646Z"
|
last_updated: "2026-06-13T12:28:27.751Z"
|
||||||
last_activity: 2026-06-13 -- Phase 16 execution started
|
last_activity: 2026-06-13 -- Phase 16 execution started
|
||||||
progress:
|
progress:
|
||||||
total_phases: 19
|
total_phases: 19
|
||||||
completed_phases: 6
|
completed_phases: 7
|
||||||
total_plans: 23
|
total_plans: 23
|
||||||
completed_plans: 22
|
completed_plans: 23
|
||||||
percent: 32
|
percent: 37
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -27,7 +27,7 @@ See: .planning/PROJECT.md (updated 2026-06-10)
|
|||||||
|
|
||||||
Phase: 16 (ci-dependency-audit-and-security-checks) — EXECUTING
|
Phase: 16 (ci-dependency-audit-and-security-checks) — EXECUTING
|
||||||
Plan: 6 of 6
|
Plan: 6 of 6
|
||||||
Status: Ready to execute
|
Status: Phase complete — ready for verification
|
||||||
Last activity: 2026-06-13 -- Phase 16 execution started
|
Last activity: 2026-06-13 -- Phase 16 execution started
|
||||||
|
|
||||||
### Deferred Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)
|
### Deferred Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)
|
||||||
@@ -241,7 +241,7 @@ Recent decisions affecting current work:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-13T12:24:49.635Z
|
Last session: 2026-06-13T12:28:17.736Z
|
||||||
Stopped at: Completed 16-05-PLAN.md
|
Stopped at: Completed 16-05-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|
||||||
|
|||||||
@@ -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