Security audit
This page publishes security and code-health audit results for a11yequitas.org. Audits run at each release.
Last run: 2026-07-19 (code health + dependency scan; other categories last ran 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-07-19 | B (79) | Score 79 / 100 from fallow health --score (fallow-cli 2.85.0). Dead-code analysis is fully clean: 0 issues (1 documented suppression, 0 stale), 110 entry points, dead files 0.0 %, dead exports 0.0 %, no circular deps. Metrics: 24,940 LOC analyzed, avg maintainability 95.3 (good), avg cyclomatic 1.9 / p90 4, 42 / 638 functions above complexity threshold. Reported duplication is 49.1 % across 65 files — the large majority is the intentional English/Spanish page-pair mirroring (each hand-translated page.es.tsx is a deliberate structural clone of its English sibling), now included in analysis since the Spanish siblings were added as entry points; this is bilingual architecture, not copy-paste debt. Previously-open findings all resolved 2026-07-19: public/static/styles.css is referenced by five live legacy HTML pages (excluded from JS-graph analysis with a documented reason); AIDoc/SRReport type exports un-exported (in-file use only); /pagefind/pagefind.js added to ignoreUnresolvedImports (generated at build time, expected). Numeric score refreshes when the audit runs on the production host. |
| Dependency scan (pnpm audit) | 2026-07-19 | A | 0 critical, 0 high, 0 moderate, 0 low. Six transitive devDependency advisories (2 high: tmp path traversal GHSA-ph9p-34f9-6g65, vite fs.deny bypass GHSA-fx2h-pf6j-xcff; 3 moderate: uuid GHSA-w5hq-g745-h8pq, launch-editor GHSA-v6wh-96g9-6wx3, js-yaml merge-key DoS; 1 low: tmp symlink GHSA-52f5-9888-hmc6) resolved 2026-07-19 via scoped pnpm overrides — parent API usage was verified compatible before forcing each bump. Prior postcss advisory (GHSA-qx2v-qp2m-jg93) remains resolved by the earlier override. |
| 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 pages pass axe-core's automated WCAG 2.2 AAA rule checks in happy-dom (a subset of full AAA — manual/AT review still required). 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. 42 of 638 analyzed functions remain above complexity threshold (the analyzed universe grew on 2026-07-19 when Spanish page siblings and CLI scripts were added as entry points). Tracked. - Fallow code health: duplication 49.1 % — the headline number rose on 2026-07-19 because the hand-translated Spanish page siblings (
page.es.tsx) were added to analysis as entry points; each is a deliberate structural clone of its English page, so most reported duplication is intentional bilingual architecture, not copy-paste debt. The genuine content-clone remainder (WCAG bullet lists, accessible table markup, "Keyboard interaction" sections shared across tutorials) still needs shared content components (e.g.,<WcagBulletList>) and is scoped for a content refactor cycle. - Fallow dead-export / unused findings — resolved 2026-07-19 —
public/static/styles.cssis referenced by five live legacy HTML pages (excluded from JS-graph analysis with a documented reason);AIDocandSRReporttype exports were un-exported (in-file use only); the manually-run CLI scripts underscripts/and the Spanishpage.es.tsxsiblings were added as declared entry points. Dead-code analysis now reports 0 issues. - Fallow unresolved import (expected, config-suppressed) —
/pagefind/pagefind.jsis generated at build time intoout/pagefind/and loaded dynamically from the same origin. Added toignoreUnresolvedImportsin.fallowrc.jsonon 2026-07-19 with this rationale. Not a security finding. - 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.
