Introduce .agents/ (PLAN.md with locked architectural anchors, phase roadmap under phases/todo/) and AGENTS.md rules for agents working in the repo. Queues the pending phases: fix history XSS, simulate fetch commands, and nginx security headers.
6.1 KiB
PLAN — Developer Homepage
Status: live static site · Last reviewed: 2026-09-18 (security & code-quality audit)
1. Project Identity
A single-page personal portfolio (Reese Wells — self-hosting & infrastructure) with an interactive fake terminal (easter-egg commands, vim simulator, achievement system). No backend, no user accounts, no PII beyond public contact details and GPG public keys.
2. Assumptions & Design Principles
- Static first. The site is plain HTML/CSS/JS. No runtime dependencies, no CDNs, no build-time JS frameworks — the supply chain is the repo itself.
- Boring deployment.
build.sh→ hasheddist/→nginx:alpineon:8080(plain HTTP) → TLS terminated by the external Caddy reverse proxy. - Test the artifact. E2E tests run against the built
dist/, notsrc/, so what is tested is exactly what is deployed. - Security headers are a container property. All hardening headers are emitted by nginx (the only thing that serves the site in production).
- Fun is safe. The fake terminal's easter eggs (page-wipe, login screen,
vim) are DOM-only pranks — no real network or system effects. The only
real network behavior (curl/wget
fetch) is being removed — see Phase 02.
3. Architectural Anchors (LOCKED DECISIONS)
| COMPONENT | DECISION | RATIONALE | STATUS |
|---|---|---|---|
| Frontend runtime | Vanilla HTML/CSS/JS, flat src/*.js files, defer load order |
No toolchain, auditable, works with build.sh hashing |
LOCKED (pre-existing) |
| Terminal modules | terminal-commands.js (canned data) · terminal-achievements.js · terminal-vim.js · terminal.js (core) |
Flat modules keep build.sh's root-level hashing working; single source of truth for command data |
LOCKED (2026-09-18 refactor) |
| Build | build.sh: md5 content-hash cache-busting + sed reference rewrites → dist/ |
md5 is a cache key, not a security hash — acceptable | LOCKED (pre-existing) |
| Serving | nginx:alpine, listen 8080, plain HTTP |
TLS is added by the Caddy edge in front of the container | LOCKED (pre-existing) |
| Edge | Caddy reverse proxy (Route53 DNS-validated TLS) in front of the container | HSTS/TLS are edge responsibilities | LOCKED (assumed — verify per Phase 03) |
| E2E tests | @playwright/test pinned 1.62.0 (matches cached chromium-1234), serves dist/ via python3 -m http.server 8123, config playwright.config.js |
Tests the deployed artifact; pinned to avoid browser re-downloads | LOCKED (2026-09-18, user-approved) |
| Deployment | Docker image → gitea.reeseapps.com/services/homepage via Gitea Actions (secrets-based registry login) |
Self-hosted CI | LOCKED (pre-existing) |
| Persistence | localStorage only (achievements, key reese-terminal-achievements) |
No backend by design | LOCKED (pre-existing) |
4. High-Level Architecture
src/ (index.html, style.css, script.js, terminal*.js, assets)
│ build.sh (hash + rewrite)
▼
dist/ (index.html + hashed assets) ◄── playwright webServer (port 8123)
│ Dockerfile (COPY → nginx html root) ◄── tests/e2e/*.spec.js (44 tests)
▼
nginx:alpine :8080 (HTTP, security headers, cache policy)
▲
Caddy edge (TLS, HSTS) ← reeseapps.com
src/terminal.js— terminal core: prompt loop, history, tab completion, dispatch, expand/collapse (expandTerminal/collapseTerminal, shared withscript.jshero click).src/terminal-commands.js—TERMINAL_COMMANDS(canned outputs; values may be functions evaluated at dispatch),TERMINAL_COMMAND_LIST(tab completion),getCommandOutput().src/terminal-vim.js—launchVimSimulator({content, terminal, mobileInput, filename, setVimMode}).src/terminal-achievements.js—ACHIEVEMENTS, localStorage persistence, toasts, hidden-section reveal,initTerminalAchievements().src/script.js— nav menu, IntersectionObserver fades, server-rack background generation, hero-click terminal toggle.
5. Validation / Verification Workflow
./build.sh— rebuilddist/.npm test(Playwright) — 44 E2E tests againstdist/on port 8123. Every phase's final pass must leave the suite green..agents/validate.sh(installed by thephased-executionskill) — must run 1 + 2 and, for Phase 03, the header checks below.- Container header check (Phase 03):
scripts/check-headers.sh— builds the image, runs it on a scratch port, asserts the four security headers on/,/index.html, and a hashed asset.
6. Data Model
No server-side data. Client-side state:
| Key | Where | Shape |
|---|---|---|
reese-terminal-achievements |
localStorage | JSON array of achievement ids (e.g. ["time_flies","nice_try"]) |
Session-only state (no persistence): terminal history, isRoot, vim state,
menu open/closed, terminal grown/collapsed.
7. Roadmap
| Phase | Title | Source |
|---|---|---|
01_fix_history_xss |
Fix terminal history-recall XSS (M-1) | audit finding M-1 |
02_simulate_fetch_commands |
Remove unvalidated client fetch from curl/wget (M-2) | audit finding M-2 |
03_nginx_security_headers |
Security headers on every location (M-4) + server_tokens off (L-2) |
audit findings M-4, L-2 |
Dismissed: audit finding M-3 (README 0.0.0.0:8080 binding) — false
positive per project owner (2026-09-18); the host firewall already restricts
access and the site is fronted by the Caddy edge.
Completed in-session (not phased): code-quality pass 2026-09-18 — module
split (Q-1), dead-code removal (Q-2/3/4, scrollOffset), expand/collapse
helpers (Q-5), rack-decoration CSS classes (Q-7), hidden-attribute reveal
(Q-8), a11y label on terminal input (Q-9), dynamic date (Q-10), vim dd
line-deletion fix, Playwright regression suite (44 tests). See
.agents/remediation_plan.md.
Remaining low-priority audit items (not yet phased): L-1 (verify HSTS at the
Caddy edge — folded into Phase 03 verification), L-3 (drop
style-src 'unsafe-inline' after moving inline style="" attrs to classes),
L-5 (label the displayed SSH key as fictitious or show fingerprint only),
L-7 (pin CI action SHAs / image signing), L-8 (build.sh verification test in
CI).