Status: Draft. Decisions and acceptance criteria below are a starting point for discussion, not a finalized plan. Feedback welcome before scoping.
Summary
Add a GDPR-compliant cookie consent flow to the non-FedRAMP portals that currently load Google Analytics without an opt-in gate. Use a self-hosted open-source banner (orestbida/cookieconsent), hard-block GA until the user accepts, and use CloudFront access logs as the server-side consent audit trail so we avoid standing up a backend or adopting a third-party CMP.
FedRAMP portals are out of scope for this issue and will be handled separately — GA itself is not FedRAMP authorized, and the right answer there is a cookieless self-hosted analytics tool, not a consent banner.
Context
- Portals currently inject
gtag.js on page load, setting _ga / _ga_* cookies before consent. Not GDPR compliant.
- Commercial CMPs add recurring cost, a third-party script on every page, and another data processor to disclose.
- We do not use AdSense / Ad Manager / AdMob, so Google's IAB-TCF Certified-CMP mandate (orestbida/cookieconsent discussion #523) does not apply.
- GA4 Google Signals should be verified off on each property before shipping — if on, GA pulls in ads consent signals we otherwise don't need.
Decisions
- Library: orestbida/cookieconsent (MIT, vanilla, ~30KB). Styled via CSS custom properties to match findable-ui tokens.
- Categories:
necessary + analytics only.
- GA pattern: hard-block.
gtag.js injected only after analytics grant; _ga* cleared and page reloaded on revoke. Consent Mode v2 signals set for forward compatibility.
- Client receipt: library's default localStorage record
{consentId, categories, revision, timestamp}. Footer "Cookie preferences" link reopens the panel.
- Server audit trail: on accept/reject/change, fire one
GET /consent/{consentId}/{revision}/{categoriesHash} → CloudFront behavior returns 204. Access logs to S3, queryable via Athena.
- Revision versioning: bump
revision to invalidate stale receipts and re-prompt.
- Geo-targeting: show banner to all users.
- Retention: S3 lifecycle on consent-log prefix, duration TBD with legal.
Architecture
Browser CloudFront + S3 (existing SPA host)
┌────────────────────────┐ ┌────────────────────────────┐
│ SPA loads │◀─────────│ index.html, JS, CSS │
│ ↓ cookieconsent init │ └────────────────────────────┘
│ ↓ read localStorage │
│ ↓ valid + current? │ ┌────────────────────────────┐
│ yes → inject GA │ accept │ /consent/* behavior │
│ no → banner │─────────▶│ returns 204 │
│ ↓ accept │ GET └─────────────┬──────────────┘
│ write receipt │ │ access logs
│ beacon /consent/... │ ▼
│ inject gtag.js │ ┌────────────────────────────┐
└────────────────────────┘ │ S3 logs + lifecycle │
│ + Athena table │
└────────────────────────────┘
Scope
- One shared consent package in the monorepo, consumed by each non-FedRAMP portal.
- Theming hooks mapping
--cc-* onto findable-ui tokens.
- CloudFront behavior + S3 log bucket + lifecycle via existing IaC pattern.
- Athena table DDL committed with the infra.
- Privacy policy updates on each affected portal.
Out of scope: FedRAMP portals, migration to cookieless analytics, IAB TCF integration.
Acceptance criteria
Spike / open questions
- Confirm GA4 Google Signals is off on each property.
- Confirm log retention duration with legal.
- Same CloudFront distribution as the SPA or dedicated one for
/consent/*?
- Shared-package location: findable-ui or new package?
References
Summary
Add a GDPR-compliant cookie consent flow to the non-FedRAMP portals that currently load Google Analytics without an opt-in gate. Use a self-hosted open-source banner (orestbida/cookieconsent), hard-block GA until the user accepts, and use CloudFront access logs as the server-side consent audit trail so we avoid standing up a backend or adopting a third-party CMP.
FedRAMP portals are out of scope for this issue and will be handled separately — GA itself is not FedRAMP authorized, and the right answer there is a cookieless self-hosted analytics tool, not a consent banner.
Context
gtag.json page load, setting_ga/_ga_*cookies before consent. Not GDPR compliant.Decisions
necessary+analyticsonly.gtag.jsinjected only afteranalyticsgrant;_ga*cleared and page reloaded on revoke. Consent Mode v2 signals set for forward compatibility.{consentId, categories, revision, timestamp}. Footer "Cookie preferences" link reopens the panel.GET /consent/{consentId}/{revision}/{categoriesHash}→ CloudFront behavior returns 204. Access logs to S3, queryable via Athena.revisionto invalidate stale receipts and re-prompt.Architecture
Scope
--cc-*onto findable-ui tokens.Out of scope: FedRAMP portals, migration to cookieless analytics, IAB TCF integration.
Acceptance criteria
_ga*cookies before consent.analyticsloadsgtag.jsand GA flows normally._ga*and prevents further GA activity this session./consent/{consentId}/{revision}/{categoriesHash}.consentId.revisionre-prompts previously consented users.Spike / open questions
/consent/*?References