GDPR Article 7(3) and CAN-SPAM §7704(a)(4) require opt-outs to be honored within a defined window — but "honored" means demonstrably processed end-to-end, not just acknowledged at the API boundary. CWE-778 (Insufficient Logging) applies directly: if the opt-out worker writes only a consent record but no audit log events, you cannot reconstruct when the job started, how long ESP suppression took, or whether the 10-business-day CAN-SPAM window was met for any specific request. When a data subject or regulator asks for the processing timeline of their opt-out, a consent record showing granted = false with no operational context is an incomplete answer.
Low because the opt-out itself is processed correctly — the logging gap creates a compliance demonstration problem rather than an active data subject rights violation, but that distinction disappears under a regulatory inquiry.
Add audit log writes at each stage of the opt-out worker's processing pipeline, not just at completion:
// src/workers/optout.ts
queue.process('process-optout', async (job) => {
const { contactId, scope, requestedAt } = job.data
await logAuditEvent({ event: 'optout-processing-started', contactId, scope, requestedAt, startedAt: new Date() })
await db.consentRecord.create({ data: { contactId, scope, granted: false, source: 'unsubscribe-link' } })
await logAuditEvent({ event: 'optout-consent-record-created', contactId, scope })
await emailPlatform.suppressContact(contactId)
await logAuditEvent({ event: 'optout-esp-synced', contactId, scope, espConfirmedAt: new Date() })
const processingMs = Date.now() - new Date(requestedAt).getTime()
await logAuditEvent({ event: 'optout-processing-complete', contactId, scope, processingMs })
})
With requestedAt from the job payload and espConfirmedAt from the final log entry, you can compute the exact processing duration for any opt-out request and prove CAN-SPAM compliance on demand.
ID: compliance-consent-engine.compliance-audit-trail.optout-events-logged
Severity: low
What to look for: Verify that the opt-out processing pipeline writes audit log entries — not just consent records. There is a difference between "we have a consent record showing granted=false" and "our audit trail shows the opt-out was requested at 2:14 AM, enqueued at 2:14 AM, processed by worker at 2:16 AM, and ESP suppression confirmed at 2:17 AM." The latter demonstrates that the regulatory window was met and the system operated correctly. Check whether the opt-out worker writes processing events to the audit log.
Pass criteria: The opt-out worker creates audit log entries at each processing stage: request received, job enqueued, job started, suppression cascade completed, ESP sync confirmed. The log entries include timing data sufficient to demonstrate the regulatory window was met. Count all audit log write calls in the opt-out worker — at least 3 must exist across the processing pipeline.
Fail criteria: Opt-out processing creates consent records but no operational audit log entries. There is no way to reconstruct the processing timeline for a specific opt-out request from the audit trail.
Skip (N/A) when: No opt-out processing pipeline exists.
Detail on fail: "Opt-out worker inserts consent_record with granted=false but writes no audit_log entry — processing timeline cannot be reconstructed" or "Audit log shows request received but not processing stages — cannot demonstrate 10-business-day compliance"
Remediation: Add audit log calls throughout the processing pipeline:
queue.process('process-optout', async (job) => {
const { contactId, scope, requestedAt } = job.data
await logAuditEvent({ event: 'optout-processing-started', contactId, scope, requestedAt, startedAt: new Date() })
await db.consentRecord.create({ data: { contactId, scope, granted: false, source: 'unsubscribe-link' } })
await logAuditEvent({ event: 'optout-consent-record-created', contactId, scope })
await emailPlatform.suppressContact(contactId)
await logAuditEvent({ event: 'optout-esp-synced', contactId, scope, espConfirmedAt: new Date() })
const processingMs = Date.now() - new Date(requestedAt).getTime()
await logAuditEvent({ event: 'optout-processing-complete', contactId, scope, processingMs })
})