Skip to content
prod 352bb92
Browse

4 · Accessibility & cookie compliance

Objective — run the WCAG 2.1 AA checks (automated scanners + manual verification) and build the GDPR/CCPA cookie inventory with consent gating that actually blocks non-essential cookies — the audits that keep the app usable for everyone and keep it legal.

Steps at a glance:

  1. Audit accessibility (WCAG 2.1 AA) — Run automated scanners (WAVE, axe DevTools) on key pages, then verify keyboard/focus/contrast with browser automation when available and human review for judgment-heavy calls.
  2. Verify cookie compliance (GDPR / CCPA) — Inventory every cookie, then verify consent actually gates the non-essential ones.

The audits that keep the app usable for everyone — and keep it legal. Automated tools catch deterministic failures, and browser automation can drive tab order, focus, and consent-banner behavior. Human review remains for judgment-heavy accessibility and legal adequacy calls.

Use Zaj-PROJECT.md to record the current accessibility/cookie posture: scanner/date, consent mechanism, cookie-policy URL, legal-review owner, and unresolved issues. Detailed evidence belongs in the Phase 10 report; unresolved blockers go to Zaj-BACKLOG.md.

Run automated scanners (WAVE, axe DevTools) on key pages, then verify keyboard/focus behavior with browser automation where available.

CheckRequirement
Keyboard navLogical tab order, visible focus, no traps
Focus indicatorNever removed globally; :focus { outline: 2px solid … }
ImagesDescriptive alt; alt="" for decorative
Form inputsEvery visible input has a <label for> or aria-label
Color contrast4.5:1 normal text · 3:1 large text & UI
Icon buttonsaria-label or .sr-only text
StructureSemantic landmarks, skip link, sequential headings

Agent-runnable greps for the deterministic failures (the scanners catch the rest):

Terminal window
# Images missing alt text
grep -rn "<img" resources/views/ --include="*.blade.php" | grep -v "alt=" || echo "OK all <img> have alt"
# Inputs that may lack a label (excluding hidden)
grep -rn "<input" resources/views/ --include="*.blade.php" | grep -v 'type="hidden"' | head -20
# Icon-only elements missing an accessible name
grep -rn 'class=".*fa-' resources/views/ --include="*.blade.php" | grep -v "aria-" || echo "OK icons labelled"
# Landmark / semantic elements present in layouts
grep -rn "<main\|<nav\|<header\|<footer" resources/views/layouts/ --include="*.blade.php"

Then add visible focus styles (never remove the outline globally) and a skip link to #main-content.

  1. Scan key pages, then verify the interactive checks against every row of the table above.

    • ✅ Automated scanners are clean and the interactive checks (tab order, focus, contrast) all pass with evidence.

Inventory every cookie, then verify consent actually gates the non-essential ones.

  1. Inventory all cookies (name, expiry, HttpOnly, Secure, SameSite) and classify them Essential / Functional / Analytics / Marketing.

    • ✅ Every cookie is listed with its attributes and a category.
  2. Verify session security — session cookies need HttpOnly=true, Secure=true, SameSite=lax.

    Terminal window
    php artisan tinker --execute="echo json_encode(\Illuminate\Support\Arr::only(config('session'), ['secure','http_only','same_site']));"
    # Expected: {"secure":true,"http_only":true,"same_site":"lax"} (production/staging APP_URL=https://…)
    • ✅ Runtime session config reports secure, http_only, and same_site=lax (not just env() lines in config/session.php).
  3. Scan for custom cookies in app code and enforce the lifetime ceilings. Every custom cookie must carry the same security attributes as the session cookie.

    Terminal window
    grep -rnE "Cookie::make|cookie\(|setcookie|->cookie\(" app/ routes/ --include="*.php"
    # Expected: each hit uses HttpOnly + Secure + SameSite; none sets a raw long-lived plaintext cookie

    Lifetime ceilings:

    CookieMax lifetime
    Session120 min
    Remember-me30 days
    Consent1 year
    • ✅ Every custom cookie sets HttpOnly + Secure + SameSite, and no cookie exceeds its lifetime ceiling (session ≤ 120 min, remember-me ≤ 30 days, consent ≤ 1 year).
  4. Test the banner in incognito: only essential cookies pre-consent; “Accept All” loads analytics/marketing; “Reject” keeps them blocked; preferences are changeable later.

    • ✅ Pre-consent loads only essential cookies; Accept/Reject behave correctly; preferences are editable later.
  5. Use one consent mechanism only — if you use a third-party manager, disable the built-in one. Confirm the cookie policy page matches reality.

    • ✅ A single consent mechanism is active and the cookie policy page matches the real inventory.

Do not mark this step done until every box below is checked.

  • 🔀 WCAG 2.1 AA passes — scanners clean; keyboard, focus, contrast, and structure verified by browser automation where available plus human review for judgment-heavy calls.
  • 🤖 Cookies inventoried — every cookie listed with attributes and a category.
  • 🤖 Session cookies secure — runtime config('session') has secure, http_only, same_site=lax.
  • 🔀 Banner gates non-essential — incognito browser test confirms Accept/Reject and editable preferences; legal adequacy reviewed by the human.
  • 🔀 One consent mechanism — built-in disabled if a third-party manager is used; policy page matches.
  • 🤖 Project state updatedZaj-PROJECT.md records scanner/date, consent mechanism, cookie-policy URL, legal-review owner, and any Zaj-BACKLOG.md accessibility/cookie blocker rows.