GDPR Art. 7(1) places the burden of proof on the controller to demonstrate that consent was obtained. Art. 5(2) (accountability) reinforces this: the controller must be able to show, on request, that consent was lawful. A stored boolean 'true' fails this standard — it records that something was accepted, not when, under what banner version, or for which specific purposes. Recital 42 notes that consent should include the time of consent and information about what was consented to. Without a timestamped, structured record, the controller cannot demonstrate compliance to a DPA or defend against a subject access request.
Medium because absent timestamp and version metadata makes it impossible to demonstrate lawful consent under GDPR Art. 7(1) and Art. 5(2), but the underlying processing may still be lawful if consent was genuinely given.
Store a structured consent record that includes per-category decisions, timestamp, version, and the method of consent. This record supports both re-consent logic and regulatory accountability.
// src/lib/consent.ts
export type ConsentRecord = {
necessary: true
analytics: boolean
marketing: boolean
timestamp: string // ISO 8601
version: string
method: 'accept_all' | 'reject_all' | 'custom'
}
// On Accept All:
saveConsent({ analytics: true, marketing: true }, 'accept_all')
// On Reject All:
saveConsent({ analytics: false, marketing: false }, 'reject_all')
// On custom save:
saveConsent({ analytics, marketing }, 'custom')
For authenticated users, send this record to your server (a consent_records table) so you can produce an audit trail across devices and sessions if a regulator requests it.
ID: cookie-consent-compliance.consent-enforcement.consent-records-timestamped
Severity: medium
What to look for: Inspect the stored consent object structure. Find where localStorage.setItem (or the equivalent cookie set) is called in the consent save handler. Check the shape of the stored value — does it include: (1) per-category booleans (analytics, marketing, etc.), (2) a timestamp (ISO 8601 string of when the user made their choice), (3) a consent version (to trigger re-consent when cookies change), (4) optionally the user's decision method ("accept_all", "reject_all", "custom"). Regulators (GDPR Article 7, Recital 42) require that the controller be able to demonstrate that the data subject consented — having a timestamped record is part of that demonstration. Check whether the stored consent can be retrieved and displayed to the user (e.g., in a cookie preferences page showing "You last consented on [date]").
Pass criteria: Count all consent record fields stored. The stored consent object includes at minimum: per-category decisions (not just a single boolean), a timestamp, and a version. Ideally also the consent method (accept_all vs. custom). The format is parseable and the timestamp is a valid ISO 8601 string. At least 3 fields must be recorded: timestamp, consent version, and user choices.
Fail criteria: Consent stored as a single boolean (localStorage.setItem('consented', 'true')). No timestamp in the stored object. No per-category breakdown in the stored record — just "accepted" or "declined" globally.
Skip (N/A) when: No consent banner exists (already failing at banner-first-visit).
Detail on fail: Example: "Consent stored as a single string 'true' at key 'cookieConsent'. No per-category breakdown, no timestamp, no version." or "Consent object has {analytics: true} but no timestamp or version field.".
Remediation: Store a structured consent record. The lib/consent.ts implementation in the consent-state-persisted remediation already includes timestamp and version. Additionally capture the consent method:
export type ConsentRecord = {
necessary: true
analytics: boolean
marketing: boolean
timestamp: string // ISO 8601 — when user consented
version: string // banner version at time of consent
method: 'accept_all' | 'reject_all' | 'custom' // how user chose
}
// When user clicks "Accept All":
saveConsent({ analytics: true, marketing: true }, 'accept_all')
// When user clicks "Reject All":
saveConsent({ analytics: false, marketing: false }, 'reject_all')
// When user saves custom preferences:
saveConsent({ analytics, marketing }, 'custom')
This record can also be sent to your server for authenticated users, providing an audit trail demonstrating that consent was obtained.