Skip to content

Audit fixes: security, accessibility, test isolation, and CI/infra#37

Open
kmatzen wants to merge 6 commits into
mainfrom
claude/project-audit-improvements-5dfkhz
Open

Audit fixes: security, accessibility, test isolation, and CI/infra#37
kmatzen wants to merge 6 commits into
mainfrom
claude/project-audit-improvements-5dfkhz

Conversation

@kmatzen

@kmatzen kmatzen commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Implements fixes for issues found in the project audit (#25#36).

Security

Correctness / reliability

Accessibility (#28)

  • Radio/likert inputs: replaced display:none (which removed them from tab order and the accessibility tree) with a visually-hidden-but-focusable pattern, plus a :focus-within outline.
  • Image lightbox: added a reusable attachLightboxTrigger() (focusable, Enter/Space) and gave the lightbox dialog semantics with focus move-in, Tab trap, and focus restore on close.
  • Identity images: focusable with Enter/Space toggling the mask overlay and aria-pressed state.
  • Retake modal: dialog semantics, focus management, Escape-to-cancel, Tab trap; also fixes a listener leak that re-added the backdrop handler on every open.

Infrastructure / tooling

Tests (#34)

  • Added tests/test_migration.py (old→JSON schema conversion + idempotency) and tests/test_security.py (security headers, DB isolation, admin auth). Migration coverage rose from 36% → 84%.

Verification

  • Python: 68 passed, 1 skipped (up from 62), overall coverage 83%.
  • Frontend: 99 passed.
  • script.js passes node --check; app imports cleanly.

Not included (tracked as follow-ups)

🤖 Generated with Claude Code


Generated by Claude Code

claude added 4 commits July 21, 2026 15:37
Address several issues from the project audit:

- Fix stored XSS in the admin dashboard by HTML-escaping all
  participant-supplied values before interpolating into innerHTML (#25).
- Harden admin auth: constant-time password comparison via hmac, and
  refuse to start in production with the default password (#26).
- Fix test DB isolation so get_db() honors app.config['DATABASE'];
  tests no longer read/write the checked-in survey.db (#27).
- Configure SQLite for concurrency (busy timeout + WAL) (#29).
- Add GitHub Actions CI running pytest and jest (#30).
- Add Dockerfile, .env.example, and CONTRIBUTING; fix .dockerignore
  that was stripping the README from the build context (#31).
- Remove dead legacy code: sample_questions.py and the unused
  build_column_map helper/import (#35).
- Add tests for the DB migration path, security headers, DB isolation,
  and admin auth (#34), raising migration coverage from 36% to 84%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
- Radio and likert inputs: replace display:none (which removed them from
  tab order and the accessibility tree) with a visually-hidden-but-focusable
  pattern, plus a :focus-within outline so keyboard users see focus.
- Image lightbox: add a reusable attachLightboxTrigger() that makes zoom
  images focusable (role=button, tabindex, Enter/Space); give the lightbox
  dialog semantics (role=dialog, aria-modal), move focus in on open, trap
  Tab, and restore focus to the trigger on close.
- Identity images: make focusable (tabindex) with Enter/Space toggling the
  mask overlay and aria-pressed reflecting state.
- Retake modal: add dialog semantics, focus management, Escape-to-cancel,
  and a Tab focus trap; also fixes a listener leak where the backdrop-click
  handler was re-added on every open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
The committed lock file was out of sync with the resolved dependency
versions, causing the new frontend CI job (which uses `npm ci`, requiring
an exact lock match) to fail. Regenerated the lock file; `npm ci` now
installs cleanly and all 99 jest tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
Error handling (#36):
- checkDevMode now checks response.ok and shows a persistent, role=alert
  error if the survey config can't load, instead of silently rendering an
  empty survey.
- showError guards against a missing active section and supports a
  persistent (non-auto-hiding) mode.
- Mask image now has an onerror handler.

De-duplication (#33):
- IDENTITY_COLORS in script.js is now the single source of truth for the
  identity/mask palette: thumbnail border colors are set inline from it and
  the 10 duplicated CSS border-color rules are removed.

Admin flatten de-duplication (#32, safe part):
- Extracted the copy-pasted JSON->row flattening from admin_results and
  admin_export into a single _flatten_result_row() helper plus a shared
  SELECT constant, preserving each endpoint's exact output (results keeps
  old-style preferred_method_* fields; export unchanged).

Verified: 68 Python tests and 99 jest tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
claude added 2 commits July 24, 2026 21:25
Adds a "UI screenshots" CI job that boots the app (DEV_MODE, gunicorn),
drives Playwright/Chromium through the main surfaces -- demographics page,
survey trial view, admin login, admin dashboard -- and uploads the PNGs as
a `ui-screenshots` artifact. This gives every PR a downloadable visual
preview from the Checks tab, since the Flask app can't use GitHub Pages.

- scripts/screenshots.py captures each page independently (best-effort, so
  one failure never aborts the rest) and honors PW_EXECUTABLE_PATH for
  local runs against a preinstalled Chromium.
- CI installs its own Chromium via `playwright install --with-deps`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
The screenshot job now captures the demographics and survey-trial pages at
both desktop (1280) and mobile (390) widths, so PR previews show the
responsive layout (stacked tiles, compact A/B buttons, Likert labels).
Admin pages remain desktop-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gJuKPCoiDH8Dq96hMBQAG
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants