From 47b7b4fc23070274918a07db89fcb4a4a87259d7 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Fri, 23 Oct 2026 00:07:13 -0400 Subject: [PATCH] phase: 03_nginx_security_headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Phase 03 final verification — all green** - Verified (all tasks already in `complete/`): snippet/Dockerfile/nginx.conf wiring, `scripts/check-headers.sh`, guardrail teeth, restored config byte-identical. - Tests (full suite): `./build.sh` → OK; `npm test` → **46 passed, 0 failed**; `bash .agents/validate.sh` → **exit 0** (46/46 + `HEADER CHECK PASSED` + hygiene gates); no leftover podman images. - Completion criteria: - `nginx-security-headers.inc` with 4 `add_header … always;` lines — ✅ - `nginx.conf`: 0 inline security `add_header` (cache headers only), 3/3 locations `include` snippet — ✅ - `server_tokens off;` in server block — ✅ (live proof: `Server: nginx`, no version) - `./scripts/check-headers.sh` → `HEADER CHECK PASSED`, OK on `/`, `/index.html`, hashed asset — ✅ (also confirmed all 4 headers + correct Cache-Control on 404 fallback) - `./build.sh && npm test` fully green — ✅ - No behavior change (terminal/vim/achievements/menu suites) — ✅ - Negative check re-run: dropping one `include` → `FAIL … on /style..css`, exit 1 — ✅ - Docs: README.md & AGENTS.md reviewed in full against actual state (commands, ports 8123/18099, header mechanism, CI tags vs `.gitea/workflows/build-push.yml`, Node ≥20 engine floor, innerHTML/neofetch exception at `terminal.js:487`, simulated curl/wget guards) — **verified consistent, no changes needed**. - Notable: check-headers.sh aborts at the first failing path via `set -e` before printing `HEADER CHECK FAILED` — README's wording matches this exactly (verified empirically). PLAN.md's "44 tests" count is stale (now 46) but PLAN.md is locked — left for the user. - No live-infra changes (no HSTS in container; edge/Caddy untouched). - Next pending phase: `04_huntington_role`. --- .../03_nginx_security_headers/00_phase.md | 0 .../01_extract_header_snippet.md | 0 .../02_add_header_verification.md | 0 ...03_nginx_security_headers__00_phase.a1.err | 0 .../03_nginx_security_headers__00_phase.a1.md | 16 + ...nx_security_headers__00_phase.a1.md.status | 1 + ...inx_security_headers__00_phase.a1.validate | 437 ++++++++++++++++++ ..._headers__01_extract_header_snippet.a1.err | 0 ...y_headers__01_extract_header_snippet.a1.md | 18 + ...rs__01_extract_header_snippet.a1.md.status | 1 + ...ers__01_extract_header_snippet.a1.validate | 428 +++++++++++++++++ ...headers__02_add_header_verification.a1.err | 0 ..._headers__02_add_header_verification.a1.md | 18 + ...s__02_add_header_verification.a1.md.status | 1 + ...rs__02_add_header_verification.a1.validate | 437 ++++++++++++++++++ Dockerfile | 4 +- README.md | 44 +- nginx-security-headers.inc | 7 + nginx.conf | 10 +- scripts/check-headers.sh | 53 +++ 20 files changed, 1459 insertions(+), 16 deletions(-) rename .agents/phases/{todo => complete}/03_nginx_security_headers/00_phase.md (100%) rename .agents/phases/{todo => complete}/03_nginx_security_headers/01_extract_header_snippet.md (100%) rename .agents/phases/{todo => complete}/03_nginx_security_headers/02_add_header_verification.md (100%) create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.err create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md.status create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.validate create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.err create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md.status create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.validate create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.err create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md.status create mode 100644 .agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.validate create mode 100644 nginx-security-headers.inc create mode 100755 scripts/check-headers.sh diff --git a/.agents/phases/todo/03_nginx_security_headers/00_phase.md b/.agents/phases/complete/03_nginx_security_headers/00_phase.md similarity index 100% rename from .agents/phases/todo/03_nginx_security_headers/00_phase.md rename to .agents/phases/complete/03_nginx_security_headers/00_phase.md diff --git a/.agents/phases/todo/03_nginx_security_headers/01_extract_header_snippet.md b/.agents/phases/complete/03_nginx_security_headers/01_extract_header_snippet.md similarity index 100% rename from .agents/phases/todo/03_nginx_security_headers/01_extract_header_snippet.md rename to .agents/phases/complete/03_nginx_security_headers/01_extract_header_snippet.md diff --git a/.agents/phases/todo/03_nginx_security_headers/02_add_header_verification.md b/.agents/phases/complete/03_nginx_security_headers/02_add_header_verification.md similarity index 100% rename from .agents/phases/todo/03_nginx_security_headers/02_add_header_verification.md rename to .agents/phases/complete/03_nginx_security_headers/02_add_header_verification.md diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.err b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md new file mode 100644 index 0000000..7fb36d8 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md @@ -0,0 +1,16 @@ +**Phase 03 final verification — all green** + +- Verified (all tasks already in `complete/`): snippet/Dockerfile/nginx.conf wiring, `scripts/check-headers.sh`, guardrail teeth, restored config byte-identical. +- Tests (full suite): `./build.sh` → OK; `npm test` → **46 passed, 0 failed**; `bash .agents/validate.sh` → **exit 0** (46/46 + `HEADER CHECK PASSED` + hygiene gates); no leftover podman images. +- Completion criteria: + - `nginx-security-headers.inc` with 4 `add_header … always;` lines — ✅ + - `nginx.conf`: 0 inline security `add_header` (cache headers only), 3/3 locations `include` snippet — ✅ + - `server_tokens off;` in server block — ✅ (live proof: `Server: nginx`, no version) + - `./scripts/check-headers.sh` → `HEADER CHECK PASSED`, OK on `/`, `/index.html`, hashed asset — ✅ (also confirmed all 4 headers + correct Cache-Control on 404 fallback) + - `./build.sh && npm test` fully green — ✅ + - No behavior change (terminal/vim/achievements/menu suites) — ✅ + - Negative check re-run: dropping one `include` → `FAIL … on /style..css`, exit 1 — ✅ +- Docs: README.md & AGENTS.md reviewed in full against actual state (commands, ports 8123/18099, header mechanism, CI tags vs `.gitea/workflows/build-push.yml`, Node ≥20 engine floor, innerHTML/neofetch exception at `terminal.js:487`, simulated curl/wget guards) — **verified consistent, no changes needed**. +- Notable: check-headers.sh aborts at the first failing path via `set -e` before printing `HEADER CHECK FAILED` — README's wording matches this exactly (verified empirically). PLAN.md's "44 tests" count is stale (now 46) but PLAN.md is locked — left for the user. +- No live-infra changes (no HSTS in container; edge/Caddy untouched). +- Next pending phase: `04_huntington_role`. diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md.status b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.validate b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.validate new file mode 100644 index 0000000..2eed57f --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__00_phase.a1.validate @@ -0,0 +1,437 @@ +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:38] "GET /index.html HTTP/1.1" 200 - + +Running 46 tests using 1 worker + +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:39] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 1 [chromium] › tests/e2e/01-page.spec.js:9:5 › Page structure › loads with correct title and lang (1.0s) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 2 [chromium] › tests/e2e/01-page.spec.js:14:5 › Page structure › nav has all seven section links pointing at existing targets (493ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:40] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 3 [chromium] › tests/e2e/01-page.spec.js:24:5 › Page structure › all major sections exist and are in the DOM (486ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 4 [chromium] › tests/e2e/01-page.spec.js:30:5 › Page structure › profile image loads (462ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:41] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 5 [chromium] › tests/e2e/01-page.spec.js:38:5 › Page structure › server rack background is generated with a terminal unit (442ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 6 [chromium] › tests/e2e/01-page.spec.js:45:5 › Page structure › GPG section shows both fingerprints and key blocks (504ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:42] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 7 [chromium] › tests/e2e/01-page.spec.js:59:5 › Page structure › external links all carry rel="noopener" (467ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - + ✓ 8 [chromium] › tests/e2e/01-page.spec.js:68:5 › Page structure › hashed assets are referenced and served (459ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:43] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 9 [chromium] › tests/e2e/02-terminal.spec.js:11:5 › Fake terminal — basic commands › hero click expands the terminal and shifts the hero (486ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 10 [chromium] › tests/e2e/02-terminal.spec.js:17:5 › Fake terminal — basic commands › initial boot lines are rendered (524ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:44] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 11 [chromium] › tests/e2e/02-terminal.spec.js:23:5 › Fake terminal — basic commands › uptime shows the load-average line (520ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 12 [chromium] › tests/e2e/02-terminal.spec.js:28:5 › Fake terminal — basic commands › ls lists the portfolio files (533ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:45] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 13 [chromium] › tests/e2e/02-terminal.spec.js:36:5 › Fake terminal — basic commands › pwd / whoami / hostname / id (565ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 14 [chromium] › tests/e2e/02-terminal.spec.js:47:5 › Fake terminal — basic commands › date shows a plausible current date (527ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:46] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 15 [chromium] › tests/e2e/02-terminal.spec.js:53:5 › Fake terminal — basic commands › uname and cat /etc/os-release (571ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 16 [chromium] › tests/e2e/02-terminal.spec.js:62:5 › Fake terminal — basic commands › echo prints its argument as plain text (512ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:47] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 17 [chromium] › tests/e2e/02-terminal.spec.js:67:5 › Fake terminal — basic commands › ss shows listening ports (514ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 18 [chromium] › tests/e2e/02-terminal.spec.js:74:5 › Fake terminal — basic commands › docker ps and podman ps (539ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:48] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 19 [chromium] › tests/e2e/02-terminal.spec.js:85:5 › Fake terminal — basic commands › ps aux and df -h and free -h (570ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:49] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 20 [chromium] › tests/e2e/02-terminal.spec.js:94:5 › Fake terminal — basic commands › neofetch renders the ASCII logo and colored blocks (552ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 21 [chromium] › tests/e2e/02-terminal.spec.js:102:5 › Fake terminal — basic commands › sl prints the locomotive (537ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:50] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 22 [chromium] › tests/e2e/02-terminal.spec.js:107:5 › Fake terminal — basic commands › help lists the available commands (522ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 23 [chromium] › tests/e2e/02-terminal.spec.js:115:5 › Fake terminal — basic commands › systemctl, ip addr, cat ssh key (590ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:51] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 24 [chromium] › tests/e2e/02-terminal.spec.js:124:5 › Fake terminal — basic commands › unknown command produces no output and a fresh prompt (527ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 25 [chromium] › tests/e2e/02-terminal.spec.js:139:5 › Fake terminal — state and special commands › history: ArrowUp/ArrowDown recall previous commands (560ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:52] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 26 [chromium] › tests/e2e/02-terminal.spec.js:156:5 › Fake terminal — state and special commands › history recall renders markup payloads as literal text (no DOM injection) (853ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:53] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 27 [chromium] › tests/e2e/02-terminal.spec.js:195:5 › Fake terminal — state and special commands › Tab completes a command (590ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 28 [chromium] › tests/e2e/02-terminal.spec.js:203:5 › Fake terminal — state and special commands › Tab cycles between multiple matches (620ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:54] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 29 [chromium] › tests/e2e/02-terminal.spec.js:218:5 › Fake terminal — state and special commands › clear empties the terminal and leaves one prompt line (616ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:55] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 30 [chromium] › tests/e2e/02-terminal.spec.js:226:5 › Fake terminal — state and special commands › sudo su - switches the prompt to root (#) (609ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 31 [chromium] › tests/e2e/02-terminal.spec.js:239:5 › Fake terminal — state and special commands › rm -rf / as a normal user is refused (617ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:56] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 32 [chromium] › tests/e2e/02-terminal.spec.js:247:5 › Fake terminal — state and special commands › rm -rf / as root triggers the destruction easter egg (1.3s) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 33 [chromium] › tests/e2e/02-terminal.spec.js:257:5 › Fake terminal — state and special commands › exit as a normal user swaps the page for a login screen (592ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:05:58] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 34 [chromium] › tests/e2e/02-terminal.spec.js:273:5 › Fake terminal — state and special commands › curl/wget are simulated — no real network request leaves the origin (3.0s) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:01] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 35 [chromium] › tests/e2e/02-terminal.spec.js:303:5 › Fake terminal — state and special commands › backspace edits the current command (521ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 36 [chromium] › tests/e2e/03-vim.spec.js:15:5 › Fake terminal — vim simulator › vim with no filename shows the splash and q exits (601ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 37 [chromium] › tests/e2e/03-vim.spec.js:26:5 › Fake terminal — vim simulator › vim with a filename opens a buffer; insert, save & quit (592ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:03] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 38 [chromium] › tests/e2e/03-vim.spec.js:48:5 › Fake terminal — vim simulator › vim normal-mode navigation and line deletion (564ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 39 [chromium] › tests/e2e/04-achievements.spec.js:8:5 › Achievements system › starts hidden until the first unlock (451ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:04] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 40 [chromium] › tests/e2e/04-achievements.spec.js:14:5 › Achievements system › uptime unlock shows a toast, persists to localStorage, reveals the section (544ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET / HTTP/1.1" 304 - + ✓ 41 [chromium] › tests/e2e/04-achievements.spec.js:43:5 › Achievements system › unlocks survive a page reload (localStorage restore) (593ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:05] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET / HTTP/1.1" 304 - + ✓ 42 [chromium] › tests/e2e/04-achievements.spec.js:55:5 › Achievements system › special achievements: sudo, nice-try rm, vim splash (692ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:06] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 43 [chromium] › tests/e2e/04-achievements.spec.js:80:5 › Achievements system › multiple unlocks stack toasts and increment the counter (634ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 44 [chromium] › tests/e2e/05-menu.spec.js:5:5 › Navigation menu › desktop: inline nav visible, hamburger hidden (517ms) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:07] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 45 [chromium] › tests/e2e/05-menu.spec.js:12:5 › Navigation menu › mobile: hamburger opens and closes the menu (1.8s) +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [23/Oct/2026 00:06:09] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 46 [chromium] › tests/e2e/05-menu.spec.js:30:5 › Navigation menu › clicking outside closes an open mobile menu (568ms) + + 46 passed (31.2s) + +To open last HTML report run: + + npx playwright show-report + +>> building image (podman)... +time="2026-10-23T00:07:12-04:00" level=warning msg="HEALTHCHECK is not supported for OCI image format and will be ignored. Must use `docker` format" +aa4dc0bf3ca88d13f0427c0786e4fc36886cfce016b2af262a0c41d687d8d540 +>> starting container on 127.0.0.1:18099... +>> checking headers... +OK: all security headers present on / +OK: all security headers present on /index.html +OK: all security headers present on /style.342793b0a19255e7ce615729a45053fe.css +HEADER CHECK PASSED diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.err b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md new file mode 100644 index 0000000..e9d75a0 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md @@ -0,0 +1,18 @@ +Task complete and fully green. + +**Implemented** +- `nginx-security-headers.inc` (new, repo root): the four hardening headers as `add_header … always;` lines (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, CSP). +- `nginx.conf`: removed all inline security `add_header` lines from the `server` block; added `server_tokens off;`; all three locations (`= /index.html`, hashed-asset regex, `/`) now `include /etc/nginx/snippets/security-headers.inc;` — fixes the `add_header` inheritance trap (M-4) and L-2. +- `Dockerfile`: `mkdir -p /etc/nginx/snippets` in the existing `RUN`, plus `COPY nginx-security-headers.inc /etc/nginx/snippets/security-headers.inc` before the `nginx.conf` COPY. +- Docs: README "Serving & configuration" rewritten (stale "Known gap M-4" paragraph replaced with the snippet mechanism + rule for new locations); AGENTS.md gained a convention banning inline security `add_header` in `nginx.conf`. + +**Verification** +- `nginx -t` in `nginx:alpine` with both files bind-mounted: "syntax is ok / test is successful". +- Ad-hoc container proof (`podman build` → run on 127.0.0.1:18099 → `curl -sI`, container + image removed afterwards): all four headers present on `/`, `/index.html` and `style..css` with their correct `Cache-Control`, and `Server: nginx` (version hidden). Not committed as a script — `scripts/check-headers.sh` is task 02's deliverable. +- `./build.sh && npm test` → **46 passed**; `bash .agents/validate.sh` → **exit 0** (46/46 + repo-hygiene gates). Coverage criterion (>90% of modified config) is met by exercising all three location paths above; formal coverage gate lands with task 02's script. + +**Decisions / deviations** +- Made no live-infrastructure changes: no deploy, no edge/Caddy touch, no HSTS inside the 8080 container (locked). Only ephemeral local podman builds/runs on a scratch port, cleaned up. +- Kept `Cache-Control`/`Pragma`/`Expires` inline in their locations (cache policy is per-location; only security headers are shared). + +**Next pending task:** `.agents/phases/todo/03_nginx_security_headers/02_add_header_verification.md` (`scripts/check-headers.sh`). diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md.status b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.validate b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.validate new file mode 100644 index 0000000..21f2436 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__01_extract_header_snippet.a1.validate @@ -0,0 +1,428 @@ +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:32] "GET /index.html HTTP/1.1" 200 - + +Running 46 tests using 1 worker + +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 1 [chromium] › tests/e2e/01-page.spec.js:9:5 › Page structure › loads with correct title and lang (597ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:33] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 2 [chromium] › tests/e2e/01-page.spec.js:14:5 › Page structure › nav has all seven section links pointing at existing targets (536ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:34] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 3 [chromium] › tests/e2e/01-page.spec.js:24:5 › Page structure › all major sections exist and are in the DOM (512ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 4 [chromium] › tests/e2e/01-page.spec.js:30:5 › Page structure › profile image loads (531ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:35] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 5 [chromium] › tests/e2e/01-page.spec.js:38:5 › Page structure › server rack background is generated with a terminal unit (509ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 6 [chromium] › tests/e2e/01-page.spec.js:45:5 › Page structure › GPG section shows both fingerprints and key blocks (537ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:36] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 7 [chromium] › tests/e2e/01-page.spec.js:59:5 › Page structure › external links all carry rel="noopener" (532ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - + ✓ 8 [chromium] › tests/e2e/01-page.spec.js:68:5 › Page structure › hashed assets are referenced and served (537ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:37] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 9 [chromium] › tests/e2e/02-terminal.spec.js:11:5 › Fake terminal — basic commands › hero click expands the terminal and shifts the hero (562ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 10 [chromium] › tests/e2e/02-terminal.spec.js:17:5 › Fake terminal — basic commands › initial boot lines are rendered (612ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:38] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 11 [chromium] › tests/e2e/02-terminal.spec.js:23:5 › Fake terminal — basic commands › uptime shows the load-average line (611ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:39] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 12 [chromium] › tests/e2e/02-terminal.spec.js:28:5 › Fake terminal — basic commands › ls lists the portfolio files (584ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 13 [chromium] › tests/e2e/02-terminal.spec.js:36:5 › Fake terminal — basic commands › pwd / whoami / hostname / id (670ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:40] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 14 [chromium] › tests/e2e/02-terminal.spec.js:47:5 › Fake terminal — basic commands › date shows a plausible current date (584ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:41] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 15 [chromium] › tests/e2e/02-terminal.spec.js:53:5 › Fake terminal — basic commands › uname and cat /etc/os-release (651ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 16 [chromium] › tests/e2e/02-terminal.spec.js:62:5 › Fake terminal — basic commands › echo prints its argument as plain text (595ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:42] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 17 [chromium] › tests/e2e/02-terminal.spec.js:67:5 › Fake terminal — basic commands › ss shows listening ports (579ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 18 [chromium] › tests/e2e/02-terminal.spec.js:74:5 › Fake terminal — basic commands › docker ps and podman ps (622ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:43] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 19 [chromium] › tests/e2e/02-terminal.spec.js:85:5 › Fake terminal — basic commands › ps aux and df -h and free -h (636ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:44] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 20 [chromium] › tests/e2e/02-terminal.spec.js:94:5 › Fake terminal — basic commands › neofetch renders the ASCII logo and colored blocks (597ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 21 [chromium] › tests/e2e/02-terminal.spec.js:102:5 › Fake terminal — basic commands › sl prints the locomotive (541ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:45] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 22 [chromium] › tests/e2e/02-terminal.spec.js:107:5 › Fake terminal — basic commands › help lists the available commands (555ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 23 [chromium] › tests/e2e/02-terminal.spec.js:115:5 › Fake terminal — basic commands › systemctl, ip addr, cat ssh key (581ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:46] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 24 [chromium] › tests/e2e/02-terminal.spec.js:124:5 › Fake terminal — basic commands › unknown command produces no output and a fresh prompt (572ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:47] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 25 [chromium] › tests/e2e/02-terminal.spec.js:139:5 › Fake terminal — state and special commands › history: ArrowUp/ArrowDown recall previous commands (652ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 26 [chromium] › tests/e2e/02-terminal.spec.js:156:5 › Fake terminal — state and special commands › history recall renders markup payloads as literal text (no DOM injection) (939ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:48] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 27 [chromium] › tests/e2e/02-terminal.spec.js:195:5 › Fake terminal — state and special commands › Tab completes a command (594ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:49] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 28 [chromium] › tests/e2e/02-terminal.spec.js:203:5 › Fake terminal — state and special commands › Tab cycles between multiple matches (567ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 29 [chromium] › tests/e2e/02-terminal.spec.js:218:5 › Fake terminal — state and special commands › clear empties the terminal and leaves one prompt line (544ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:50] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 30 [chromium] › tests/e2e/02-terminal.spec.js:226:5 › Fake terminal — state and special commands › sudo su - switches the prompt to root (#) (577ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 31 [chromium] › tests/e2e/02-terminal.spec.js:239:5 › Fake terminal — state and special commands › rm -rf / as a normal user is refused (544ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:51] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 32 [chromium] › tests/e2e/02-terminal.spec.js:247:5 › Fake terminal — state and special commands › rm -rf / as root triggers the destruction easter egg (1.3s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 33 [chromium] › tests/e2e/02-terminal.spec.js:257:5 › Fake terminal — state and special commands › exit as a normal user swaps the page for a login screen (565ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:53] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 34 [chromium] › tests/e2e/02-terminal.spec.js:273:5 › Fake terminal — state and special commands › curl/wget are simulated — no real network request leaves the origin (3.0s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:56] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 35 [chromium] › tests/e2e/02-terminal.spec.js:303:5 › Fake terminal — state and special commands › backspace edits the current command (522ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 36 [chromium] › tests/e2e/03-vim.spec.js:15:5 › Fake terminal — vim simulator › vim with no filename shows the splash and q exits (596ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:57] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 37 [chromium] › tests/e2e/03-vim.spec.js:26:5 › Fake terminal — vim simulator › vim with a filename opens a buffer; insert, save & quit (649ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:58] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 38 [chromium] › tests/e2e/03-vim.spec.js:48:5 › Fake terminal — vim simulator › vim normal-mode navigation and line deletion (645ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 39 [chromium] › tests/e2e/04-achievements.spec.js:8:5 › Achievements system › starts hidden until the first unlock (484ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:24:59] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 40 [chromium] › tests/e2e/04-achievements.spec.js:14:5 › Achievements system › uptime unlock shows a toast, persists to localStorage, reveals the section (573ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET / HTTP/1.1" 304 - + ✓ 41 [chromium] › tests/e2e/04-achievements.spec.js:43:5 › Achievements system › unlocks survive a page reload (localStorage restore) (652ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:00] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET / HTTP/1.1" 304 - + ✓ 42 [chromium] › tests/e2e/04-achievements.spec.js:55:5 › Achievements system › special achievements: sudo, nice-try rm, vim splash (786ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:01] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 43 [chromium] › tests/e2e/04-achievements.spec.js:80:5 › Achievements system › multiple unlocks stack toasts and increment the counter (620ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 44 [chromium] › tests/e2e/05-menu.spec.js:5:5 › Navigation menu › desktop: inline nav visible, hamburger hidden (518ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 45 [chromium] › tests/e2e/05-menu.spec.js:12:5 › Navigation menu › mobile: hamburger opens and closes the menu (1.8s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:25:04] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 46 [chromium] › tests/e2e/05-menu.spec.js:30:5 › Navigation menu › clicking outside closes an open mobile menu (553ms) + + 46 passed (32.4s) + +To open last HTML report run: + + npx playwright show-report + diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.err b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md new file mode 100644 index 0000000..51ffb4a --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md @@ -0,0 +1,18 @@ +**Implemented** +- `scripts/check-headers.sh` (new, mode 755) — builds the real nginx image with podman/docker, serves it on `127.0.0.1:18099`, asserts all four hardening headers on `/`, `/index.html` and the first hashed asset; content is byte-identical to the task spec. +- `.agents/validate.sh` — added the Phase 03 gate call to `./scripts/check-headers.sh` (per PLAN §5.3), plus cleanup of the leftover `homepage-headercheck-*` image tag, and a loud `WARN:` skip on hosts with no container runtime. + +**Verification** +- `./scripts/check-headers.sh` → exit 0: `OK: all security headers present on /`, `/index.html`, `/style.342793b0….css`, then `HEADER CHECK PASSED`. +- Negative check (teeth): removed the snippet `include` from the hashed-asset location → script exited 1 with four `FAIL:
missing on /style.…css`; restored and confirmed `sha256sum -c nginx.conf: OK` + `diff` clean. +- Manual container dump: 4 headers present on `/`, `/index.html`, hashed asset and the SPA-fallback path; `Server: nginx` (L-2 holds); per-location `Cache-Control` unchanged. Config coverage = 3/3 locations exercised (100% > 90%). +- `./build.sh && npm test` → **46 passed**. `bash .agents/validate.sh` → **exit 0** (46 E2E + header check + repo-hygiene gates). No container/image or port leftovers. + +**Docs** +- `README.md`: new “Verify the headers” subsection (what the script does, scratch port 18099, `HEADER CHECK PASSED`, no local build needed, runtime requirement) and updated the validation-gate paragraph. +- `AGENTS.md`: gate description now includes the container header check; nginx-header convention notes `check-headers.sh` is the regression guard for the per-location `include` rule. + +**Notes / deviations** +- Kept the script exactly as specified: because of `set -e` it aborts at the first failing path, so the `HEADER CHECK FAILED` summary line is unreachable (exit code and `FAIL:` lines are still correct). Documented in README rather than editing spec'd content. +- No live-infra changes (no deploy, no Caddy edge, no HSTS in the 8080 container); only ephemeral local podman builds/runs, all cleaned up. CI (`.gitea/workflows/build-push.yml`) still has no test/header step — that's unphased PLAN item L-8, left untouched. +- Next pending task: `.agents/phases/todo/04_huntington_role/01_add_timeline_entry.md` (phase 03 task list is exhausted; harness can run `00_phase.md` final pass / move this task to complete). diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md.status b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.validate b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.validate new file mode 100644 index 0000000..8528e63 --- /dev/null +++ b/.agents/reports/03_nginx_security_headers/03_nginx_security_headers__02_add_header_verification.a1.validate @@ -0,0 +1,437 @@ +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /index.html HTTP/1.1" 200 - + +Running 46 tests using 1 worker + +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:53] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 1 [chromium] › tests/e2e/01-page.spec.js:9:5 › Page structure › loads with correct title and lang (581ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 2 [chromium] › tests/e2e/01-page.spec.js:14:5 › Page structure › nav has all seven section links pointing at existing targets (547ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:54] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 3 [chromium] › tests/e2e/01-page.spec.js:24:5 › Page structure › all major sections exist and are in the DOM (470ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 4 [chromium] › tests/e2e/01-page.spec.js:30:5 › Page structure › profile image loads (486ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:55] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 5 [chromium] › tests/e2e/01-page.spec.js:38:5 › Page structure › server rack background is generated with a terminal unit (468ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 6 [chromium] › tests/e2e/01-page.spec.js:45:5 › Page structure › GPG section shows both fingerprints and key blocks (480ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:56] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 7 [chromium] › tests/e2e/01-page.spec.js:59:5 › Page structure › external links all carry rel="noopener" (497ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - + ✓ 8 [chromium] › tests/e2e/01-page.spec.js:68:5 › Page structure › hashed assets are referenced and served (480ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:57] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 9 [chromium] › tests/e2e/02-terminal.spec.js:11:5 › Fake terminal — basic commands › hero click expands the terminal and shifts the hero (549ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 10 [chromium] › tests/e2e/02-terminal.spec.js:17:5 › Fake terminal — basic commands › initial boot lines are rendered (545ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:58] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 11 [chromium] › tests/e2e/02-terminal.spec.js:23:5 › Fake terminal — basic commands › uptime shows the load-average line (554ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 12 [chromium] › tests/e2e/02-terminal.spec.js:28:5 › Fake terminal — basic commands › ls lists the portfolio files (563ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:45:59] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 13 [chromium] › tests/e2e/02-terminal.spec.js:36:5 › Fake terminal — basic commands › pwd / whoami / hostname / id (656ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:00] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 14 [chromium] › tests/e2e/02-terminal.spec.js:47:5 › Fake terminal — basic commands › date shows a plausible current date (577ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 15 [chromium] › tests/e2e/02-terminal.spec.js:53:5 › Fake terminal — basic commands › uname and cat /etc/os-release (589ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:01] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 16 [chromium] › tests/e2e/02-terminal.spec.js:62:5 › Fake terminal — basic commands › echo prints its argument as plain text (571ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 17 [chromium] › tests/e2e/02-terminal.spec.js:67:5 › Fake terminal — basic commands › ss shows listening ports (602ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:02] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 18 [chromium] › tests/e2e/02-terminal.spec.js:74:5 › Fake terminal — basic commands › docker ps and podman ps (607ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:03] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 19 [chromium] › tests/e2e/02-terminal.spec.js:85:5 › Fake terminal — basic commands › ps aux and df -h and free -h (628ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 20 [chromium] › tests/e2e/02-terminal.spec.js:94:5 › Fake terminal — basic commands › neofetch renders the ASCII logo and colored blocks (566ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:04] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 21 [chromium] › tests/e2e/02-terminal.spec.js:102:5 › Fake terminal — basic commands › sl prints the locomotive (542ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 22 [chromium] › tests/e2e/02-terminal.spec.js:107:5 › Fake terminal — basic commands › help lists the available commands (522ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:05] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 23 [chromium] › tests/e2e/02-terminal.spec.js:115:5 › Fake terminal — basic commands › systemctl, ip addr, cat ssh key (596ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 24 [chromium] › tests/e2e/02-terminal.spec.js:124:5 › Fake terminal — basic commands › unknown command produces no output and a fresh prompt (527ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:06] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 25 [chromium] › tests/e2e/02-terminal.spec.js:139:5 › Fake terminal — state and special commands › history: ArrowUp/ArrowDown recall previous commands (583ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:07] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 26 [chromium] › tests/e2e/02-terminal.spec.js:156:5 › Fake terminal — state and special commands › history recall renders markup payloads as literal text (no DOM injection) (859ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 27 [chromium] › tests/e2e/02-terminal.spec.js:195:5 › Fake terminal — state and special commands › Tab completes a command (529ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:08] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 28 [chromium] › tests/e2e/02-terminal.spec.js:203:5 › Fake terminal — state and special commands › Tab cycles between multiple matches (544ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:09] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 29 [chromium] › tests/e2e/02-terminal.spec.js:218:5 › Fake terminal — state and special commands › clear empties the terminal and leaves one prompt line (562ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 30 [chromium] › tests/e2e/02-terminal.spec.js:226:5 › Fake terminal — state and special commands › sudo su - switches the prompt to root (#) (581ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:10] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 31 [chromium] › tests/e2e/02-terminal.spec.js:239:5 › Fake terminal — state and special commands › rm -rf / as a normal user is refused (535ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:11] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 32 [chromium] › tests/e2e/02-terminal.spec.js:247:5 › Fake terminal — state and special commands › rm -rf / as root triggers the destruction easter egg (1.2s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 33 [chromium] › tests/e2e/02-terminal.spec.js:257:5 › Fake terminal — state and special commands › exit as a normal user swaps the page for a login screen (552ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:12] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 34 [chromium] › tests/e2e/02-terminal.spec.js:273:5 › Fake terminal — state and special commands › curl/wget are simulated — no real network request leaves the origin (3.1s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 35 [chromium] › tests/e2e/02-terminal.spec.js:303:5 › Fake terminal — state and special commands › backspace edits the current command (557ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:16] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 36 [chromium] › tests/e2e/03-vim.spec.js:15:5 › Fake terminal — vim simulator › vim with no filename shows the splash and q exits (579ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 37 [chromium] › tests/e2e/03-vim.spec.js:26:5 › Fake terminal — vim simulator › vim with a filename opens a buffer; insert, save & quit (572ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:17] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 38 [chromium] › tests/e2e/03-vim.spec.js:48:5 › Fake terminal — vim simulator › vim normal-mode navigation and line deletion (618ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 39 [chromium] › tests/e2e/04-achievements.spec.js:8:5 › Achievements system › starts hidden until the first unlock (452ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:18] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 40 [chromium] › tests/e2e/04-achievements.spec.js:14:5 › Achievements system › uptime unlock shows a toast, persists to localStorage, reveals the section (548ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:19] "GET / HTTP/1.1" 304 - + ✓ 41 [chromium] › tests/e2e/04-achievements.spec.js:43:5 › Achievements system › unlocks survive a page reload (localStorage restore) (559ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET / HTTP/1.1" 304 - + ✓ 42 [chromium] › tests/e2e/04-achievements.spec.js:55:5 › Achievements system › special achievements: sudo, nice-try rm, vim splash (688ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:20] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 43 [chromium] › tests/e2e/04-achievements.spec.js:80:5 › Achievements system › multiple unlocks stack toasts and increment the counter (564ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 44 [chromium] › tests/e2e/05-menu.spec.js:5:5 › Navigation menu › desktop: inline nav visible, hamburger hidden (471ms) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:21] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 45 [chromium] › tests/e2e/05-menu.spec.js:12:5 › Navigation menu › mobile: hamburger opens and closes the menu (2.3s) +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET / HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /style.342793b0a19255e7ce615729a45053fe.css HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /profile.fda2296cc373e01300dce68bdaf0991b.jpeg HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /terminal-commands.b11ee656c4a1c395dae70c8ccde84707.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /terminal-achievements.824b6020d4572150826e354689be158a.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /terminal.ec0e2ff734985cc54efeb2a46a3f262b.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /terminal-vim.9b342a7c8be670e9a96e8813c43d8edf.js HTTP/1.1" 200 - +[WebServer] 127.0.0.1 - - [22/Oct/2026 23:46:24] "GET /script.5596b80e3eb6d4ebf507689493b9c66a.js HTTP/1.1" 200 - + ✓ 46 [chromium] › tests/e2e/05-menu.spec.js:30:5 › Navigation menu › clicking outside closes an open mobile menu (650ms) + + 46 passed (31.6s) + +To open last HTML report run: + + npx playwright show-report + +>> building image (podman)... +time="2026-10-22T23:47:26-04:00" level=warning msg="HEALTHCHECK is not supported for OCI image format and will be ignored. Must use `docker` format" +83faed371f8dded42ad3456a28cff69d4f72c0f23c7b29ca83e288c2eba77ea8 +>> starting container on 127.0.0.1:18099... +>> checking headers... +OK: all security headers present on / +OK: all security headers present on /index.html +OK: all security headers present on /style.342793b0a19255e7ce615729a45053fe.css +HEADER CHECK PASSED diff --git a/Dockerfile b/Dockerfile index f7a5682..efc874c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ FROM docker.io/library/nginx:alpine RUN rm /etc/nginx/conf.d/default.conf && \ - apk add --no-cache coreutils + apk add --no-cache coreutils && \ + mkdir -p /etc/nginx/snippets +COPY nginx-security-headers.inc /etc/nginx/snippets/security-headers.inc COPY nginx.conf /etc/nginx/conf.d/default.conf COPY src/ /src/ diff --git a/README.md b/README.md index 80f26c4..dbeb273 100644 --- a/README.md +++ b/README.md @@ -34,21 +34,42 @@ Visit `http://localhost:8080` in your browser. ## Serving & configuration `nginx.conf` is copied into the image as the site config. It listens on -**8080 over plain HTTP**, serves the hashed build, declares the hardening -headers (`X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, -`Content-Security-Policy`), serves `index.html` with `no-cache`, and caches the -hashed assets as `immutable` for a year. +**8080 over plain HTTP**, serves the hashed build, serves `index.html` with +`no-cache`, and caches the hashed assets as `immutable` for a year. -Known gap (audit finding M-4, Phase 03): nginx does not merge `add_header` -across configuration levels, so the `location` blocks that set their own headers -drop the server-level security headers — measured against the built image, `/`, -`/index.html` and a hashed asset currently get only the cache headers. Every -`add_header` block must repeat the hardening headers until that phase lands. +The four hardening headers (`X-Frame-Options`, `X-Content-Type-Options`, +`Referrer-Policy`, `Content-Security-Policy`) live in +`nginx-security-headers.inc`, which the Dockerfile copies to +`/etc/nginx/snippets/security-headers.inc`. nginx does **not** merge +`add_header` across configuration levels, so every `location` block `include`s +that snippet instead of inheriting from the `server` block — otherwise the +locations that set their own cache headers would silently drop the security +headers (audit finding M-4, Phase 03). `server_tokens off;` hides the nginx +version from the `Server` header (audit finding L-2). When adding a new +`location`, `include` the snippet there too and keep the security headers out of +`nginx.conf` itself. TLS and HSTS are the job of the reverse proxy in front of the container (`reeseapps.com` terminates at the Caddy edge). Do **not** add `Strict-Transport-Security` or a `listen 443` block to `nginx.conf`. +### Verify the headers + +```bash +./scripts/check-headers.sh +``` + +This is the integration test for the header fix: it builds the **real** image +with podman (or docker), runs it on the scratch address `127.0.0.1:18099`, and +asserts all four security headers on each response class — `/`, `/index.html` +and the first hashed asset found in `index.html`. On success it prints one +`OK:` line per path and `HEADER CHECK PASSED` (exit 0); a missing header prints +`FAIL:
missing on ` and exits non-zero, aborting at the first +failing path. No local `./build.sh` is needed — the image builds itself. The +container is removed by the script; `.agents/validate.sh` removes the leftover +`homepage-headercheck-*` image tag. Run it after **any** change to +`nginx.conf`, `nginx-security-headers.inc` or the `Dockerfile`. + ## Deploy with Quadlet The `homepage.container` Quadlet unit is **not** tracked in this repository — it @@ -115,4 +136,7 @@ revision cached for the pinned Playwright version — run bumping the pin. The validation gate the phased pipeline runs per task is `.agents/validate.sh` -(full E2E suite plus repo-hygiene checks): `bash .agents/validate.sh`. +(full E2E suite, the container security-header check from +`scripts/check-headers.sh`, plus repo-hygiene checks): `bash +.agents/validate.sh`. The header check is skipped with a loud `WARN:` line on a +host with no container runtime; everything else still runs. diff --git a/nginx-security-headers.inc b/nginx-security-headers.inc new file mode 100644 index 0000000..6a3b5ec --- /dev/null +++ b/nginx-security-headers.inc @@ -0,0 +1,7 @@ +# Shared security headers — included by EVERY nginx location. +# A location that defines its own add_header inherits none from the +# server block, so each location includes this snippet directly. +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';" always; diff --git a/nginx.conf b/nginx.conf index 86343a0..8f4fbcd 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,11 +5,8 @@ server { root /usr/share/nginx/html; index index.html; - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';" always; + # Hide the nginx version in the Server header (audit L-2) + server_tokens off; # Gzip compression gzip on; @@ -19,6 +16,7 @@ server { # Never cache index.html so the latest HTML is always served location = /index.html { + include /etc/nginx/snippets/security-headers.inc; add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; @@ -27,11 +25,13 @@ server { # Cache hashed assets forever (filename contains content hash) location ~* \.(css|js|jpeg|jpg|png|gif|ico|svg|woff2?)$ { + include /etc/nginx/snippets/security-headers.inc; add_header Cache-Control "public, max-age=31536000, immutable"; try_files $uri =404; } location / { + include /etc/nginx/snippets/security-headers.inc; try_files $uri $uri/ /index.html; } diff --git a/scripts/check-headers.sh b/scripts/check-headers.sh new file mode 100755 index 0000000..71a4604 --- /dev/null +++ b/scripts/check-headers.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail +# Verify every response class (/, /index.html, a hashed asset) carries the +# full security-header set, using the REAL nginx image on a scratch port. +# Requires a container runtime (podman or docker). + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +if command -v podman >/dev/null 2>&1; then RUNTIME=podman +elif command -v docker >/dev/null 2>&1; then RUNTIME=docker +else echo "ERROR: neither podman nor docker found" >&2; exit 2; fi + +IMAGE="homepage-headercheck-$$" +PORT=18099 +CID="" +trap 'if [ -n "$CID" ]; then $RUNTIME rm -f "$CID" >/dev/null 2>&1 || true; fi' EXIT + +echo ">> building image ($RUNTIME)..." +$RUNTIME build -q -t "$IMAGE" . + +echo ">> starting container on 127.0.0.1:$PORT..." +CID="$($RUNTIME run -d --rm -p 127.0.0.1:$PORT:8080 "$IMAGE")" + +for i in $(seq 1 30); do + curl -sf -o /dev/null "http://127.0.0.1:$PORT/" && break + sleep 0.5 + if [ "$i" -eq 30 ]; then echo "ERROR: container not ready" >&2; exit 1; fi +done + +ASSET="$(curl -s "http://127.0.0.1:$PORT/" \ + | grep -oE '(style|script|terminal)[a-z-]*\.[0-9a-f]{32}\.(css|js)' | head -n1 || true)" +if [ -z "$ASSET" ]; then echo "ERROR: no hashed asset found in index.html" >&2; exit 1; fi + +fail=0 +check_headers() { + local path="$1" hdrs h + hdrs="$(curl -sI "http://127.0.0.1:$PORT$path")" + for h in "X-Frame-Options" "X-Content-Type-Options" "Referrer-Policy" "Content-Security-Policy"; do + if ! printf '%s' "$hdrs" | grep -qi "^${h}:"; then + echo "FAIL: ${h} missing on ${path}" >&2; fail=1 + fi + done + [ "$fail" -eq 0 ] && echo "OK: all security headers present on ${path}" +} + +echo ">> checking headers..." +check_headers "/" +check_headers "/index.html" +check_headers "/${ASSET}" + +if [ "$fail" -ne 0 ]; then echo "HEADER CHECK FAILED" >&2; exit 1; fi +echo "HEADER CHECK PASSED"