# Security & Code Quality Audit — homepage
**Date:** 2026-09-18
**Auditor:** pi agent (read-only audit; no fixes applied at audit time)
**Method:** manual source review (all files) + tooling: `git log`, `git grep` secret scan, `ssh-keygen` key validation, `curl` live-header probe.
## Disposition (2026-09-18, same session as the audit)
- **M-1, M-2, M-4 → scheduled as phases** under the phased-execution protocol:
`.agents/phases/todo/01_fix_history_xss/`, `02_simulate_fetch_commands/`,
`03_nginx_security_headers/` (see `.agents/PLAN.md` §7). Execute with the
`phased-execution` skill (`auto-phase.sh`).
- **M-3 → DISMISSED (false positive per project owner, 2026-09-18).** The
host firewall already restricts access to the container port and the site
is fronted by the Caddy TLS edge; the README's `0.0.0.0:8080` binding is
accepted as-is.
- **Code-quality findings (Q-1…Q-12, plus the vim `dd` dead path) → executed
directly in-session** (explicit owner instruction to run them outside the
phase pipeline): module split (`terminal.js` 1353 → 652 lines +
`terminal-commands.js` / `terminal-vim.js` / `terminal-achievements.js`),
dead-code removal, expand/collapse helpers, rack-decoration CSS classes,
`hidden`-attribute reveal, a11y label, dynamic `date`, vim line-deletion
fix. A 44-test Playwright E2E regression suite (`tests/e2e/`, run with
`./build.sh && npm test`) was created and is green.
- Remaining Low items (L-1, L-3, L-5, L-7, L-8) stay unphased; L-1 is folded
into Phase 03's verification notes, L-2 into Phase 03 task 01.
## Scope & Stack
- **Stack:** static site (vanilla HTML/CSS/JS, no framework, no build deps) → hashed via `build.sh` (md5 cache-bust + `sed` rewrites) → served by `nginx:alpine` on `:8080` (plain HTTP, TLS expected at an external Caddy edge) → Docker/Podman image pushed to `gitea.reeseapps.com/services/homepage` via Gitea Actions.
- **Core functionality:** personal portfolio with an interactive fake terminal (easter-egg commands, vim simulator, achievements in `localStorage`).
- **Data sensitivity:** none at rest. Public PII only (name, email, GPG public keys). No auth, no backend, no forms.
- **Assumptions:** TLS termination + HSTS happen at the Caddy reverse proxy in front of this container (container only listens on 8080 HTTP). Live site headers could not be verified from this network.
**Overall:** low-risk attack surface (static, no input persistence, no backend). No Critical/High findings. One Medium XSS-pattern, two Medium deployment issues, several Low hardening items, and a number of code-quality debts (dead code, duplication, untested build).
## Findings (severity-ranked)
---
### M-1. XSS sink: terminal command history recalled via `innerHTML`
- **Severity:** Medium (High if ever served without the CSP below)
- **Type:** CWE-79 (DOM-based XSS via innerHTML)
- **Location:** `src/terminal.js`, ArrowUp/ArrowDown handlers in the terminal `keydown` listener (~lines 330–370)
- **Description:** The Enter handler stores raw user input into `commandHistory` (captured via `textContent`, so typing is safe). But ArrowUp/ArrowDown recall it with:
```js
lastLine.innerHTML = '$ ' + cmdLine + ' ';
```
`cmdLine` is untrusted input injected into `innerHTML`.
- **PoC:** In the site's terminal type:
```
```
press Enter, then press ↑. The payload is injected as HTML.
Mitigated *today* by the deployed CSP `script-src 'self'` (inline handlers and inline `