Files
bchanot-cv/.claude/audits/TOUR.md
T

241 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TOUR — audit & fix log (append-only)
## Tour 2026-07-05 — REPORT-ONLY — 1 iteration — no branch, zero fixes
Mode: `--report-only` (first real run of /tour). All findings `open`/`suggested`
— nothing was modified. Checks detected: NONE (no tests/lint/build — static
site, no package manager; report line INF-1).
| ID | Axis | File | Sev | Finding | Status |
|----|------|------|-----|---------|--------|
| SEC-1 | security | Dockerfile:27 | high | no `USER` directive — nginx master runs as root in container (semgrep `missing-user`, BLOCK-class). Compose hardening (read_only, cap_drop ALL, no-new-privileges, 127.0.0.1 bind) shrinks blast radius but root master remains. Fix: `FROM nginxinc/nginx-unprivileged:1.29-alpine` (uid 101, port 8080) + adjust EXPOSE/ports/healthcheck | open |
| SEC-2 | security | nginx.conf:46-62 | med | **add_header inheritance trap**: location blocks (.html/.pdf/images) set their own `Cache-Control` → ALL 5 server-level security headers (CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) dropped on real responses. Confirmed live: pages send zero security headers, only 404 path carries them. Fix: repeat the 5 add_header in each location block (or `include headers.conf`) | open |
| SEC-3 | security | nginx.conf | med | HSTS missing end-to-end — delegated to outer TLS proxy but live site doesn't send it. Fix at the front proxy (VPS) or add here behind X-Forwarded-Proto check | open |
| SEC-4 | security | Dockerfile:4 | med | base `nginx:1.27-alpine` = retired mainline (no security fixes since 2025-04), tag-pinned without digest. Fix: bump to current stable + digest pin | open |
| SEC-5 | security | nginx.conf:26 | low | `set_real_ip_from 0.0.0.0/0` trusts X-Forwarded-For from anywhere — safe only while the 127.0.0.1 bind holds. Fix: restrict to the front proxy IP | open |
| SEC-6 | security | nginx.conf | low | no `server_tokens off` in this config; live front proxy also leaks `nginx/1.24.0 (Ubuntu)` (host-level, outside repo — VPS action) | open |
| SEC-7 | security | nginx.conf:22 | info | CSP `unsafe-inline` script/style — inherent to the documented single-file convention; script hash possible if wanted. Google Fonts = only external dep (conforms; GDPR self-host note). mailto/tel in clear = deliberate for a CV | open/accepted |
| CLN-1 | clean | index.html + CV html | - | 5 × pure-white bg (`#fff`) in `.stack-card`, `.project-card`, `.theme-card`, `.methode-item`, CV `body` — forbidden by project CLAUDE.md → `var(--page)` `#f5f3ec` | suggested |
| CLN-2 | clean | CV_Bastien_Chanot.html | - | dead CSS rule `.screen-label` (no matching element) | suggested |
| CLN-3 | clean | index.html | - | 4 card components duplicate ~80% of base+hover styles (~421 redundant lines) — collapsible into a shared `.card` base class | suggested |
| CLN-4 | clean | index.html | - | 8 colors beyond the strict 6-hex palette (`--dark-mid`, `--g900`, `--g050`, text neutrals…) — likely intentional neutrals; JUDGMENT CALL, not auto-fixable | suggested |
| CLN-5 | clean | index.html | - | CSS transitions stay active under `prefers-reduced-motion: reduce` (only animations disabled) — stricter conformity would zero transitions too | suggested |
| REC-1 | reconcile | .claude/* | - | ZERO drift. Oracles: 1369d27 exists ✓, PDF=HTML same commit 1ae73e0 (declared invariant holds) ✓, develop==origin ✓, BLK-001 resolved AND live-confirmed (favicon HTTP 200 in prod — VPS rebuild done) ✓. Open TODO items (OG image, favicon mirror into CV, mobile QA, WCAG contrast) verified genuinely open, not drift | consistent |
| DOC-1 | doc | README.md | - | Contents table omits `.githooks/` (active gitflow guard since 195188f, predates last README edit) + no clone note `git config core.hooksPath .githooks` | suggested |
| DOC-2 | doc | README.md | - | Contents table omits `.gitignore`/`.dockerignore` — conventionally skipped, low value | suggested |
| INF-1 | infra | - | - | no checks configured (tests/lint/build) — nothing to run in re-verify phase; acceptable for a zero-dependency static site | reported |
### Iterations
1. **It1 (report-only)** — 4 parallel read-only audits: security-auditor
(semgrep 1.168.0, pinned rulesets, 91 rules / 18 files → VERDICT BLOCK(1)),
cso posture (0 crit / 0 high / 3 med / 2 low / 5 info; secrets sweep of tree
+ full git history clean), clean audit (10 findings, config files clean),
doc drift (2 drifts; README otherwise accurate; README-only judged right
for this repo — DEPLOY.md split not warranted). Reconcile inline: zero
drift. Report-only ⇒ zero fixes by design ⇒ single iteration = full
picture; convergence loop N/A.
### Residuals (all — nothing fixed by design)
SEC-1 high (root in container), SEC-2/3/4 med (headers dropped / HSTS / EOL
base image), SEC-5/6 low, CLN-1..5, DOC-1/2. Highest-value single fix:
**SEC-2** (nginx add_header inheritance — live site currently serves zero
security headers).
### Suggested next step
`/tour ~/Documents/bchanot-cv` (auto mode) to fix on a `chore/tour-*` branch —
SEC-1/2/4 + CLN-1/2 are mechanical; SEC-3 needs the VPS side; CLN-3 is a
larger refactor worth its own pass; CLN-4 is the owner's judgment call.
Commits: 1 (this report — `.claude/**`, hook-exempt; no code touched).
Scratch reports (.tour-semgrep/.tour-cso/.tour-clean/.tour-doc) folded here
then deleted (STEP 3.2).
## Tour 2026-07-05 — AUTO — branch chore/tour-2026-07-05 — 2 iterations — CONVERGED
Fix pass over the 2026-07-05 report-only findings (user GO + 3 scope answers:
fix Docker path / strict palette conformity / prod vhost provided).
| ID | Axis | File | Sev | Finding | Status |
|----|------|------|-----|---------|--------|
| SEC-1 | security | Dockerfile | high | root master in container | fixed ba13d69 — `nginxinc/nginx-unprivileged:1.28-alpine` digest-pinned, uid 101 (verified `id` in container), `USER root` scoped to the one `rm`, cap_add dropped — **BREAKING**: container port 80 → 8080 (compose mapping/healthcheck updated same commit; VPS `.env PORT=2937` unaffected: mapping is `127.0.0.1:${PORT}:8080`) |
| SEC-2 | security | nginx.conf | med | add_header inheritance dropped all security headers | fixed ba13d69 — shared `nginx-security-headers.conf` snippet re-included in every location; live-style oracle in hardened container: 5/5 headers on `/`, `.html`, `.pdf`, favicon |
| SEC-3 | security | VPS vhost | med | HSTS missing end-to-end | fixed IN PROD by owner (front vhost patch) — live-verified `strict-transport-security: max-age=31536000` on bchanot.fr + www |
| SEC-4 | security | Dockerfile | med | EOL base image, tag-only pin | fixed ba13d69 (1.28-alpine stable + digest) |
| SEC-5 | security | nginx.conf | low | trust-all set_real_ip_from | fixed ba13d69 (→ 127.0.0.1, matches compose bind) |
| SEC-6 | security | nginx.conf + VPS vhost | low | server version leak | fixed ba13d69 (`server_tokens off` in-repo) + IN PROD by owner (front) — live-verified `server: nginx` |
| SEC-7 | security | snippet:12 | low/info | CSP `unsafe-inline` | open/accepted — documented convention, static no-input site (it2 semgrep sole non-blocking note) |
| CLN-1 | clean | index.html + CV | - | 5× `background:#fff` | fixed 7e7bd66 → `var(--page)` (user chose strict conformity; visual change: cards blend with parchment, borders kept) |
| CLN-2 | clean | CV html | - | dead `.screen-label` | fixed 7e7bd66 |
| CLN-5 | clean | index.html | - | transitions alive under reduced-motion | fixed 7e7bd66 (universal kill rule) |
| CLN-3 | clean | index.html | - | ~421-line card CSS duplication | open — refactor worth its own pass |
| CLN-4 | clean | index.html | - | 8 neutrals beyond strict palette | open — owner judgment call |
| REC-1 | reconcile | TODO/BDR-004 | - | prod topology CONFIRMED = BDR-004 as declared (native front proxy → container on 2937); earlier "native, no docker" premise was the misunderstanding — container IS the content server | consistent |
| DOC-1 | doc | README.md | - | .githooks row + hooksPath note; deploy section synced (unprivileged image, snippet, front/container split) | fixed 840632a |
| INV-1 | invariant | CV pdf | - | PDF regenerated with the HTML (weasyprint, same commit 7e7bd66) | held |
### Iterations
1. **It1** — fixes from the same-day report-only audit (tree unchanged since):
security ba13d69 (docker build + in-container `nginx -t` + hardened run +
4-location header oracle ALL PASS), clean 7e7bd66 (+PDF regen), doc
840632a (via doc-commit.sh). Prod side: owner applied front vhost patch
(HSTS + server_tokens), live-verified from here.
2. **It2 (convergence)** — fresh semgrep full scan: VERDICT PASS, 0 blocking
(prior Dockerfile BLOCK resolved), 1 LOW reported (SEC-7 accepted); fresh
clean re-audit: CONVERGED-CLEAN yes, prior findings resolved, zero new
(CSS braces balanced, README↔infra aligned). Zero fixes → CONVERGED.
### Residuals (open)
SEC-7 (accepted CSP convention), CLN-3 (dedup refactor), CLN-4 (palette
judgment). Prod content headers (CSP/XCTO/XFO…) appear once the fixed
container is redeployed: merge → VPS `git pull && docker compose up -d
--build` → verify `curl -sI https://bchanot.fr/ | grep -i x-content`.
Commits: 4 (fix/clean/docs + this report). BREAKING: 1 (SEC-1, container
port — compose covered). Branch left UNMERGED — `gitflow finish` on GO.
## Follow-up 2026-07-05 — residuals closed (chore/tour-residuals, user GO)
| ID | Resolution |
|----|-----------|
| CLN-3 | Card CSS deduplicated via grouped selectors (shared chrome/hover/head/title/tag blocks + per-class specifics), zero HTML change, cascade-order verified (no interfering same-specificity rules between shared and specific blocks), braces 195/195. Honest correction: the audited "~421 redundant lines" was overstated — real net dedup ≈ 60 lines. |
| CLN-4 | Norm aligned with reality: the 8 functional neutrals (inks, rule/tag, 2 green intermediates) are now DOCUMENTED as allowed in CLAUDE.md (+ README pointer). "Any color outside the two lists is a violation" keeps the norm enforceable. |
| SEC-7 | script-src hardened: `unsafe-inline` replaced by the sha256 hash of the single inline script (index has zero style/script attributes). style-src keeps `unsafe-inline` (CV carries 2 style attributes + single-file convention) — documented. NEW INVARIANT in CLAUDE.md: recompute the hash after any inline-JS edit (stale hash = JS silently blocked in prod). |
| INF-2 | CORRECTION: false positive in the 2026-07-05 report-only run — `.gitignore` exists (549B) and covers the expected classes. No action was ever needed. |
## Tour 2026-07-05-2 — AUTO — branch chore/tour-2026-07-05-2 — 2 iterations — CONVERGED
Re-run of /tour on develop (d7256ff) after the day's earlier tours merged. Goal:
verify no regression + catch anything new. Branch suffixed `-2` to keep this
header distinct from the earlier converged run. gstack OFF → optional It1 cso
posture add-on not run; the security floor (security-auditor + pinned semgrep)
ran BOTH iterations, not degraded. No package.json/Makefile → no automated
tests/lint/build; project checks = domain invariants (CSP-hash, PDF↔HTML).
| ID | Axis | File | Sev | Finding | Status |
|----|------|------|-----|---------|--------|
| SEC-1 | security | (full tree) | - | Fresh semgrep both iterations → VERDICT PASS, 0 blocking. Sole note: `style-src 'unsafe-inline'` (accepted single-file convention, It1==It2). No regression from the prior SEC fixes; script-src hash still matches inline script | pass |
| CLN-1 | clean | index.html:347 | - | dead `.reveal.d6` rule (markup uses reveal d1–d5 only) | fixed 30b0e44 |
| CLN-2 | clean | CV:408 | - | dead `position: running(siteFooter)` — no `element(siteFooter)` consumer; `.footer-bar` is `display:none` in @media print (@page auto-numbered footer replaces it); on screen running() is an invalid position value, ignored | fixed 30b0e44 |
| CLN-3 | clean | CV:438 | - | no-op `box-shadow: none` on `.page` (`.page` sets a shadow nowhere; weasyprint ignores box-shadow entirely — confirmed by its own warning) | fixed 30b0e44 |
| CLN-4 | clean | CV:315 | - | dead `.skills-grid { font-size: 8.4pt }` — every direct child is a `.skill-label`(9.5pt)/`.skill-values`(10pt) div; no bare text node, no em-dependency → never renders | fixed 30b0e44 |
| CLN-5 | clean | CV:46-48,54,316 | - | stray blank lines (triple blank before `.page`, blanks inside `.page`/`.skills-grid`) | fixed 30b0e44 |
| CLN-6 | a11y | index.html:1003,1007 | - | 2 decorative `.arrow` SVGs miss the `aria-hidden="true"` the sibling download arrow (1011) has. NOT auto-fixed: adding it changes the a11y tree → outside the clean phase's behavior-preserving scope (belongs to an a11y pass; cf. TODO "WCAG AA contrast") | open (suggested) |
| CLN-7 | norm | index.html:931 | - | `.footer` bg `#061008` is off-palette (darker than `--dark #0d1b12`, `--g900 #0e3320`). Design system documents footer = `#0d1b12`. Fix changes rendering (slightly lighter footer) → owner decision, not auto-fixed | open (suggested) |
| CLN-8 | norm | CV:252,135-136,434-435 | - | off-palette colors: `#a8d4bc` tag border (252), gradient stops `#edeadf`/`#f2efe6` (136/435), texture fill `rgba(26,71,48,0.05)` (135/434). All rendering-changing → owner decision, not auto-fixed | open (suggested) |
| CLN-9 | content | index.html vs CV:507-508 | - | profile-state wording drift: landing "Pays de la Loire / remote or 1–2j Paris" vs CV "région nantaise / hybride Nantes / 1–2j Paris" (not contradictory — Nantes ∈ PdL — but CV adds "hybride Nantes"). CLAUDE.md requires cross-file consistency → owner picks canonical wording, not auto-fixed | open (suggested) |
| REC-1 | reconcile | .claude/memory/decisions.md | - | **BDR-004 stale**: text says `nginx:1.27-alpine` / container port 80 / "HSTS omitted at container", but the real Dockerfile+compose (post 2026-07-05 SEC-1 fix) = `nginxinc/nginx-unprivileged:1.28-alpine` / port 8080 / uid 101. That tour never added a superseding decision (index stops at BDR-005). Append-only registry + tour-read-only → SUGGEST a superseding **BDR-006**. README deploy section is already correct | suggested |
| REC-2 | reconcile | .claude/memory/decisions.md | - | BDR-002 "Warnings connus: `box-shadow:none` ignoré par weasyprint" — that declaration was removed this tour (CLN-3), so the documented warning no longer fires. Minor note to add when BDR-002 is next touched | suggested |
| REC-3 | reconcile | TODO.md + registries | - | ZERO false-done. Oracles: 1369d27 exists ✓; `og:image` absent = TODO item genuinely open ✓; CV favicon-block not mirrored = open, matches BDR-005 note ✓; WCAG-contrast + real-mobile-QA open ✓; develop==origin/develop (d7256ff), branch +1 unmerged ✓; BLK-001 resolved, favicon assets present ✓ | consistent |
| DOC-1 | doc | README.md | - | doc-syncer automatic mode → `PATCHED_FILES: (none)`. Deploy section already reflects unprivileged image/port 8080 (prior tour sync); file table matches root inventory; cleanup touched nothing user-facing | no-op |
| INV-1 | invariant | index.html / CV pdf | - | CSP hash `sha256-Al1M34KxI6Ye5Viu6aO//7CYyaLzqtpG9GX95FFlSOY=` recomputed == pinned (inline `<script>` untouched) ✓; PDF regenerated byte-identical to the pre-edit baseline (text sha256 083055…96a8 + per-page PNG @150dpi render hash all match) → PDF=HTML invariant holds, PDF file unchanged | held |
### Iterations
1. **It1** — security-auditor fresh semgrep (94 rules / 25 files → VERDICT PASS,
1 accepted LOW) + read-only clean audit (13 findings: C1–C8, N1–N5). Applied
behavior-preserving fixes CLN-1..5 (commit 30b0e44); proven behavior-preserving
(PDF renders pixel-identical, CSP hash unchanged). Fresh `analyzer` re-verify:
RE-VERIFY PASS, braces balanced, zero new. Reconcile (report-only): BDR-004
drift + BDR-002 note + zero false-done. Doc: no drift.
2. **It2 (convergence)** — fresh full-tree semgrep: VERDICT PASS, identical to It1,
0 new blocking. Clean stability: 4 removed selectors GONE, braces balanced
(index 204/204, CV 68/68), known-open findings (CLN-6..9) persist = NOT new,
zero new introduced. Zero fixes → CONVERGED.
### Residuals (open — all require owner judgment, none auto-fixable behavior-preservingly)
CLN-6 (arrows aria-hidden — a11y pass), CLN-7/8 (off-palette colors — design
decision), CLN-9 (profile-state wording — copy canonicalization), REC-1
(superseding BDR-006 for the hardened container), REC-2 (BDR-002 warning note).
SEC accepted-LOW (`style-src 'unsafe-inline'`) unchanged from prior runs.
Checks: semgrep PASS (both it.), CSP-hash MATCH, PDF↔HTML byte-identical render,
CSS braces balanced. No automated tests/lint/build (static site).
Commits: 2 (clean 30b0e44 + this report). BREAKING: 0. Branch left UNMERGED.
Scratch reports (.tour-semgrep, .tour-clean, .tour-semgrep-it2) folded here then
deleted (STEP 3.2).
## Follow-up 2026-07-05-2 — all 5 residuals closed (chore/tour-2026-07-05-2, owner GO)
| ID | Resolution |
|----|-----------|
| CLN-6 | `aria-hidden="true"` added to the 2 decorative CTA arrows (match sibling download arrow). Visual identical, a11y-tree only. Commit `607124a`. |
| CLN-7 | `.footer` bg `#061008` → `var(--dark)` #0d1b12 (the design-system footer color). Commit `ede7576`. |
| CLN-8 | CV off-palette → tokens: `.tag` border `#a8d4bc` → `var(--g300)` (nearest visible green); body+print texture `rgba(26,71,48,.05)` → `rgba(27,94,59,.05)` (--g700); gradient stops `#edeadf`/`#f2efe6` → `var(--tag)`/`var(--page)`. PDF regenerated, render verified (2 pages, layout intact, page-1 eyeballed). Commit `ede7576`. |
| CLN-9 | Owner chose the CV wording as canonical (Option B): landing about-para + callout aligned to "installation région nantaise prévue" + "hybride Nantes"; `CLAUDE.md` geography note updated to match. CV unchanged. Commit `f515875` → BDR-007. |
| REC-1 | BDR-004 drift resolved by superseding entry **BDR-006** (nginx-unprivileged:1.28 / port 8080 / uid 101). |
| REC-2 | BDR-002 "box-shadow warning" note now historical (declaration removed in `30b0e44`) — left as-is (append-only registry), noted here. |
Checks: CSP-hash MATCH, braces balanced (index 204/204, CV 68/68), PDF 2 pages.
Commits: 3 fixes (`607124a`/`ede7576`/`f515875`) + capitalize (BDR-006/007, LRN-003,
journal) + this follow-up. Branch finished → develop + pushed on owner GO.
## Tour 2026-07-05-3 — AUTO — branch chore/tour-2026-07-05-3 — 3 iterations — CONVERGED
Third run of the day, on develop 7967aff (all prior tour residuals merged).
gstack ON → cso posture add-on ran it1 (it was OFF for run -2) — and caught the
run's only HIGH. Session-limit pause mid-it3 (2026-07-05→06); both it3 agents
resumed from transcript, tree unchanged, no audit gap.
| ID | Axis | File | Sev | Finding | Status |
|----|------|------|-----|---------|--------|
| SEC-1 | security | Dockerfile:5 | high | base `nginx-unprivileged:1.28-alpine` (correct this morning) now on a RETIRED stable branch — 2026-05-13 nginx batch (CVE-2026-42945, rewrite-module buffer overflow, RCE/DoS-class) fixed only in 1.30.1+/1.31.1+, never backported to 1.28.x; digest pin froze the vulnerable build | fixed 1aa97f0 — `1.30-alpine@fd3314e3…` (nginx/1.30.3). Verified: build, `nginx -t`, uid 101, hardened run 5/5 headers + HTTP 200 on /, .html, .pdf, favicon. Not BREAKING (same port/contract); prod needs rebuild+redeploy after merge |
| SEC-2 | security | (full tree) | - | semgrep floor: fresh scan ALL 3 iterations → VERDICT PASS, 0 findings (94 rules; 23→28 files as scratch reports accrued). cso it1: all same-day fixes hold; secrets sweep tree + 36-commit history clean | pass |
| CLN-1 | clean | CV:490 | - | leftover double blank after `<body>` (run -2's CLN-5 went triple→double) | fixed 613bfc0 |
| CLN-2 | clean | nginx.conf:67 | - | dead `deny all;` — `return 404` fires at rewrite phase, access phase never reached | fixed 613bfc0 — dotfile-404 oracle PASS |
| CLN-3 | clean | .dockerignore:14 | - | phantom `nginx.conf.bak` (never existed in tree or history) | fixed 613bfc0 |
| CLN-4 | clean | CV | - | duplicated rules: `.xp/.project/.edu-header`, 3 date chips (5/7 shared), `.xp-role`≡`.edu-degree`, `.lang-item`≡`.interest-tag` → shared block + per-class overrides | fixed 613bfc0 — PDF text-hash + per-page render-hash + full byte-identity vs committed PDF (LRN-003) |
| CLN-5 | clean | CV:528,585 | - | 2 byte-identical inline `style=` attrs → `.inline-link` class; snippet comment ("style attributes in the CV") synced | fixed 613bfc0 — CV now zero style attrs |
| CLN-6 | clean | index:505/761 | - | `.stack-note code`≡`.theme-list code` minus padding → grouped | fixed 613bfc0 |
| CLN-7 | clean | CV:43-44,430 | - | no-op body `margin/padding` (universal reset covers) | fixed 613bfc0 |
| CLN-8 | clean | index:700-701 | - | 2 no-op `.formation .timeline*` overrides restating base values (also removed a latent same-specificity override of the `.current` ring) | fixed 613bfc0 |
| J1 | norm | index+CV (12 sites) | - | `#fff`/rgba-white family (text-on-dark + 4% overlay) outside BOTH documented palette lists — de-facto accepted, undocumented | open — document as 3rd allowed family in CLAUDE.md, or map to tokens (visible change) |
| J2 | norm | CV headings | - | CV section titles/roles mono/sans vs CLAUDE.md "Fraunces = section titles, role headings" — deliberate compact-CV style, unflagged by all prior passes | open — document CV exception (recommended) or restyle |
| J3 | norm | CV:204 | - | `border-radius: 10px` on lang/interest tags — >6px unless counted as pills | open — owner call |
| J4 | config | nginx.conf | - | regex-location order lets hypothetical `/.foo.html` hit the caching block before the dotfile block (both still 404 — file absent; defense-in-depth only) | open — optional reorder |
| J5 | config | Dockerfile+compose | - | healthcheck duplicated (compose fully overrides image HEALTHCHECK, identical params — one always inert) | open — owner call (image stays self-checking without compose) |
| N1 | clean | both font URLs | info | unused Google Fonts faces (index: Fraunces 0,300/0,500/0,700 + DM Sans 300; CV: Fraunces 0,300/0,600 + DM Sans 300) | open — CV half provable via render-hash; index half needs a visual oracle (browser) → owner GO |
| N2 | content | CV:485/498/517 | - | date chips in English (`Apr 2026 - present`…) vs French-copy rule + hyphen/en-dash inconsistency | open — content change, owner call |
| N3 | clean | index:863-869 | info | `.contact-grid` grid declarations no-op around single child — removing `display:grid` can alter margin-collapsing, no render oracle | open |
| N4 | config | nginx.conf:39 | info | `application/pdf` in gzip_types — compressing already-flate-compressed format; removal changes observable response header | open — owner call |
| REC-1 | reconcile | TODO + registries | - | ZERO pre-existing drift. Oracles: CSP hash pinned==computed ✓, PDF↔HTML same commit ede7576 + byte-identical render ✓, BDR-006 (unprivileged/8080) + BDR-007 (Nantes wording ×2 index, ×1 CV) match tree ✓, BLK-001 COPY line holds ✓, og:image + CV-favicon TODO items genuinely open ✓, develop==origin ✓ | consistent |
| REC-2 | reconcile | decisions.md BDR-006 | - | SEC-1 bump makes BDR-006's "1.28-alpine" version detail stale (decision itself — unprivileged base + 8080 — unchanged). Registry read-only for tour | suggested — annotate via /reconcile or /capitalize |
| DOC-1 | doc | README.md:91 | - | stale `1.28-alpine` ref after SEC-1 | fixed 2f5e51a (doc-syncer automatic, single-line) |
| INV-1 | invariant | CSP + PDF | - | CSP hash MATCH all iterations (script byte-untouched); post-clean PDF byte-identical to committed (text sha256 083055…96a8 + both page render-hashes) → PDF file unchanged, nothing to regen | held |
### Iterations
1. **It1** — parallel: security-auditor (semgrep PASS 0 findings) + cso posture
(gstack ON, it1-only: 0c/1h/0m/0l/6i — the HIGH = SEC-1, sourced
endoflife.date + nginx advisories) + clean audit (8 fixable / 5 judgment).
Fixes: 1aa97f0 (security, oracle-verified) + 613bfc0 (clean F1–F8, 5 files,
net −54 lines, render-hash proof). Re-verify (fresh analyzer): PASS — cascade
safety, zero dead selectors, commits scoped. Reconcile: zero drift + REC-2.
Doc-syncer automatic: README 1.28→1.30 (2f5e51a via scoped fallback commit).
2. **It2** — fresh semgrep PASS; clean stability: it1 fixes hold (braces
203/203, 67/67), but 4 NEW info/judgment findings (N1–N4). Fix policy: none
provably behavior-preserving with available oracles (N1-index/N3 need a
browser render; N2/N4 owner calls) → 0 applied, all catalogued open. New
findings appeared → iteration 3 required.
3. **It3 (convergence, at bound)** — fresh semgrep PASS (0 findings); fresh
clean sweep against the full catalogue: stability PASS, borderline items
considered and rejected below threshold, ZERO new. Zero fixes + zero new →
CONVERGED.
### Residuals (open — all owner-judgment, none auto-fixable with available oracles)
J1 (document white family — recommended), J2 (document CV typography
exception — recommended), J3 (10px radius), J4 (dotfile regex order), J5
(healthcheck dup), N1 (font trim — CV provable, index needs eyeball), N2
(English date chips), N3 (.contact-grid), N4 (gzip pdf), REC-2 (BDR-006
version note). Standing accepted/TODO: SEC-7 CSP style-src, og:image, CV
favicon block, WCAG contrast, real-mobile QA.
### Prod follow-up
SEC-1 lands in prod only after merge: VPS `git pull && docker compose up -d
--build` → verify `curl -sI https://bchanot.fr/ | grep -i server` + container
`nginx -v` = 1.30.3.
Checks: semgrep PASS ×3, docker build + nginx -t + hardened-run 4-location
header oracle PASS, CSP-hash MATCH, PDF byte-identical, braces 203/203 + 67/67.
No automated tests/lint/build (static site). Commits: 4 (fix/clean/docs + this
report). BREAKING: 0. Branch left UNMERGED — `gitflow finish` on owner GO.
Scratch reports (.tour-semgrep ×3, .tour-cso, .tour-clean ×3) folded here then
deleted (STEP 3.2).