Security audit
This page publishes security and code-health audit results for a11yequitas.org. Audits run at each release.
Last run: 2026-05-25. Next run: on next release.
Methodology
- vitest — component and route security assertions: no dangerously set HTML outside approved inline scripts, no external script sources beyond the analytics stub, no cross-origin data leakage.
- Fallow — codebase intelligence tool (Rust). Measures dead code, duplication, cyclomatic complexity, unit size, and coupling. Produces a 0–100 health score with a letter grade. Integrated into the release checklist.
- pnpm audit — dependency vulnerability scan run on every release via
pnpm run audit(chained with Vitest and Fallow). High and critical advisories block release. - HTTP security headers — verified via automated check and manual inspection. Target headers:
Content-Security-Policy,Strict-Transport-Security,X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policy.
Grades by category
| Category | Date | Grade | Findings |
|---|---|---|---|
| Code health (Fallow) | 2026-05-25 | B (78.9) | Score 78.9 / 100 last published by server pnpm run audit (fallow-cli 2.80.0). Latest dev metrics (89 files analyzed, 41 entry points, 15,718 LOC, avg maintainability 95.8 (good), avg cyclomatic 1.9 / p90 4): 4 issues (1 suppressed, 0 stale suppressions); duplication 21.5 % across 30 files / 28 clone groups (2,419 LOC); 24 / 306 functions above complexity threshold; dead files 1.1 %, dead exports 1.2 %; no circular deps. Open findings: public/static/styles.css reported unused; AIDoc and SRReport type exports reported unused; @siteimprove/alfa-rules reported unused in tests/package.json; /pagefind/pagefind.js reported unresolved — expected, generated at build time from out/pagefind/. Numeric score refreshes when pnpm run audit runs on the production host. |
| Dependency scan (pnpm audit) | 2026-05-25 | A | 0 critical, 0 high, 0 moderate, 0 low. Prior postcss < 8.5.10 advisory (GHSA-qx2v-qp2m-jg93) resolved by pnpm override pinning postcss@<8.5.10 → ^8.5.10. All consumers (next, @tailwindcss/postcss, vite) now resolve to postcss 8.5.15. |
| HTTP security headers | 2026-05-25 | B+ | HTTPS is enforced: plain HTTP returns 301 to https://a11yequitas.org/. Live response includes Content-Security-Policy, Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, and Permissions-Policy: camera=(), microphone=(), geolocation=(). Follow-up: raise HSTS to max-age=63072000, add interest-cohort=(), and keep frame-ancestors 'none' as the effective anti-framing control. |
| CSP policy | 2026-05-25 | B | Active policy observed on live HTTPS response: default-src 'none'; scripts limited to same-origin inline code plus Umami and Cloudflare Insights; same-origin styles, images, fonts, workers, and Pagefind are allowed; base-uri 'self', form-action 'none', and frame-ancestors 'none' are set. Follow-up: remove 'unsafe-inline' and 'wasm-unsafe-eval' from script-src after migrating inline scripts to nonces or hashes, and re-check any map tile image requirements before tightening img-src. |
| XSS / injection surface | 2026-05-25 | A | Static export — no server-side rendering, no live form submissions, no eval, no Function(), no document.write, no direct .innerHTML = in production source. Test-only matches limited to src/__tests__/static-html.aaa.test.ts (document.write(html) + w.eval(axeSource)) used to load generated static HTML and inject axe into the test DOM. 12 dangerouslySetInnerHTML uses: 11 inline JSON-LD / theme-restore string literals; 1 (SearchDialog Pagefind excerpts) injects build-time HTML from same-origin index — trust boundary noted in source. |
| Third-party scripts | 2026-05-25 | A | One self-hosted Umami analytics script (defer, no cookies). No other third-party JS. No CDN-loaded fonts or icons. All target="_blank" links carry rel="noopener noreferrer". |
| Privacy / data collection | 2026-05-25 | A | No cookies. No PII collected. Font and theme preferences stored in localStorage only (strings, no identifiers). No network fetch in production (the one dev-only POST in ScreenReaderChecklist is gated by NODE_ENV !== "production"). |
| Accessibility regression (Vitest + axe) | 2026-05-25 | A | 70 / 70 static-HTML pages pass WCAG 2.2 AAA via axe-core in happy-dom. Locks in Playwright findings at the file level. Run via pnpm run audit:vitest (23.17 s). |
| Accessibility audit (Playwright + axe + Alfa) | 2026-05-25 | A | 352 / 352 tests pass. 32 routes × axe (6 theme×font combos) + Alfa per route + structural assertions (lang, h1, title, skip-link). |
Static site posture
a11yequitas.org is a fully static Next.js export. There is no server-side execution, no database, no authentication surface, and no form submission handler. The attack surface is limited to:
- Third-party analytics script injection risk (one script, defer-loaded)
- CDN / hosting layer (Podman + Nginx on Rocky Linux)
localStorage— stores font and theme preference strings only; no sensitive data
Known findings
- postcss < 8.5.10 (resolved 2026-05-24) — XSS in CSS stringify output (GHSA-qx2v-qp2m-jg93). Fixed by pnpm override in
pnpm-workspace.yaml; all consumers now resolve topostcss 8.5.15. - Fallow code health: unit_size — page components still inline 200–400 LOC of JSX content per file. Wrapper boilerplate already extracted to
ProseArticle; remaining work is to split each page's inline sections (alerts, tables, bullet lists) into co-located sub-components. 24 of 307 functions remain above complexity threshold. Tracked. - Fallow code health: duplication 22.6 %— dominant clones are repeated content text: WCAG bullet lists, accessible HTML table markup, "Keyboard interaction" sections shared across multiple tutorials (2,591 duplicated LOC, 29 clone groups across 31 files). Resolving requires extracting shared content components (e.g.,
<WcagBulletList>) and is scoped for a content refactor cycle. - Fallow dead-export / unused findings (2026-05-25) —
public/static/styles.cssreported unused;AIDocandSRReporttype exports reported unused;@siteimprove/alfa-rulesreported unused intests/package.json. Triage queued for next content refactor cycle. - Fallow unresolved import (expected) —
/pagefind/pagefind.jsreported unresolved because the Pagefind index is generated at build time intoout/pagefind/and loaded dynamically from the same origin. Not a security finding; documented here so reviewers can ignore on subsequent runs. - SearchDialog Pagefind excerpt injection (low) —
dangerouslySetInnerHTMLrenders the Pagefind index excerpt HTML. Source is the same-origin index built from this site's own content; no third-party content is indexed. Trust boundary documented inline. If the index ever ingests external content, sanitize before render.
Report a security issue
To report a security issue, contact A11y Equitas via a11yequitas.org. Please do not file public issues for unresolved security vulnerabilities.
