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

Security audit grades. A = no findings. F = critical unresolved.
CategoryDateGradeFindings
Code health (Fallow)2026-05-25B (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-25A0 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 headers2026-05-25B+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 policy2026-05-25BActive 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 surface2026-05-25AStatic 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 scripts2026-05-25AOne 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 collection2026-05-25ANo 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-25A70 / 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-25A352 / 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.css reported unused; AIDoc and SRReport type exports reported unused; @siteimprove/alfa-rules reported unused in tests/package.json. Triage queued for next content refactor cycle.
  • Fallow unresolved import (expected) /pagefind/pagefind.js reported unresolved because the Pagefind index is generated at build time into out/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.