Schedule icon
Browse icon
Return icon
Switch icon
If icon
SlackIncomingWebhook icon
Log icon

Selenium Pre-Consent Cookie Compliance Gate

Audit a page for tracking cookies set before consent using Selenium, classify the result, and alert Slack on violation.

Categories
BusinessInfrastructure

Under GDPR and similar privacy regimes, non-essential tracking cookies are only supposed to be set after a visitor consents, not the moment the page loads. Marketing tags, A/B test snippets, and third-party pixels routinely violate this, and it is invisible from the outside unless someone actually opens dev tools and checks document.cookie before touching the consent banner. This blueprint uses plugin-selenium's Browse task to do exactly that, automatically and on a schedule.

How it works

  1. audit_page_for_tracking_cookies (io.kestra.plugin.selenium.Browse) opens a session against the configured Selenium Grid, navigates to target_url, immediately reads document.cookie via an EXECUTE_SCRIPT action and filters it against tracked_cookie_names in JavaScript, then checks for a consent banner via EXTRACT_TEXT with multiple: true so a missing banner returns an empty list instead of failing the task. allowFailure: true lets the flow continue instead of aborting if the Grid or the page itself is unreachable.
  2. classify_tracking_compliance (io.kestra.plugin.core.debug.Return) checks whether outputs.audit_page_for_tracking_cookies.scriptResults.tracking_check is defined at all. If it is missing, the classification is PROBE_FAILED. If the filtered list has at least one entry, the classification is PRE_CONSENT_TRACKING_DETECTED; otherwise NO_TRACKING_DETECTED.
  3. route_by_tracking_compliance (io.kestra.plugin.core.flow.Switch) branches on that string. PRE_CONSENT_TRACKING_DETECTED runs evaluate_alert_toggle (io.kestra.plugin.core.flow.If), which pages Slack via dispatch_compliance_alert only when send_slack_alert is true, naming the matched cookies and whether a banner was even present, logging a suppressed-alert line otherwise. NO_TRACKING_DETECTED logs a clean audit line. PROBE_FAILED pages Slack directly since compliance status is now unknown. The defaults branch logs a fallback line that is unreachable under normal operation.
  4. log_audit_summary records a status line on every run regardless of branch, so execution history reads as a continuous compliance trend.
  5. The flow-level errors block pages Slack separately if the sentinel flow itself fails outright.
  6. The cookie_compliance_schedule trigger polls every 6 hours with explicit input overrides, shipped disabled: true until pointed at a real page.

What you get

  • A scheduled, real-browser proof of whether a page sets known tracking cookies before any consent interaction, not just a static source-code grep.
  • A three-way status output (NO_TRACKING_DETECTED / PRE_CONSENT_TRACKING_DETECTED / PROBE_FAILED) other flows can gate on, for example before sign-off on a new landing page.
  • A Slack page naming the exact cookies found and whether a consent banner was even present on breach or probe failure.
  • A separate failure channel for the sentinel flow itself, plus a standing per-run audit log line.

Who it's for

  • Privacy, legal, and compliance teams who need continuous proof, not a one-time audit, that marketing and analytics tags respect consent.
  • Web platform and marketing engineering teams who keep breaking consent compliance every time a new tag or pixel gets added to a page.
  • Teams building a Day 2 governance check that catches a regression the moment a deploy reintroduces an early-firing tracker.

Why orchestrate this with Kestra

A one-off manual check in dev tools catches a violation once and then nobody repeats it after the next deploy. This flow keeps every audit in Kestra's execution history, turns the result into a typed status output other flows (deployment gates, legal sign-off workflows) can depend on, and gives you an If/Switch branch to grow into real remediation (blocking a release, opening a ticket) without rewriting the probe itself.

Prerequisites

  • A running Selenium Grid (or compatible WebDriver endpoint) reachable from the Kestra Worker.
  • The target page publicly reachable from the Grid node.
  • A Slack incoming webhook for the violation, probe-failure, and flow-failure alerts.

Quick start

  1. Add the SELENIUM_GRID_URL and SLACK_WEBHOOK secrets.
  2. Run the flow once with the shipped default (https://example.com) and confirm status reads NO_TRACKING_DETECTED.
  3. Point target_url and consent_banner_selector at your real site.
  4. Enable cookie_compliance_schedule.

How to extend

  • Add a second Browse task that clicks the consent banner's accept button and re-reads document.cookie, so you can confirm tracking cookies appear only after that click, not only that they are absent before it.
  • Use the SCREENSHOT action to capture visual evidence of the banner (or its absence) alongside the cookie check, for an audit trail a non-technical reviewer can read.
  • Fan this out across every public-facing page on your site with a Loop task, reusing the same two actions per URL.
  • Push status into a KV store to alert only on state transitions instead of every run.

Secrets

  • SELENIUM_GRID_URL: URL of the Selenium Grid or WebDriver endpoint, for example http://localhost:4444.
  • SLACK_WEBHOOK: Slack incoming webhook used for the violation, probe-failure, and flow-failure alerts.
  • OSS note: if your Kestra instance reads secrets from environment variables instead of a secrets backend, set SECRET_SELENIUM_GRID_URL and SECRET_SLACK_WEBHOOK as base64, for example echo -n "<your-value>" | base64.

Inputs

  • target_url (STRING, default https://example.com): page to audit.
  • consent_banner_selector (STRING, default a generic cookie-banner selector guess): CSS selector for the consent banner; replace with your real site's selector.
  • tracked_cookie_names (ARRAY of STRING, default ["_ga", "_gid", "_gat", "_fbp", "_gcl_au"]): cookie name prefixes to check for.
  • send_slack_alert (STRING, default "true"): when "true", page Slack on a violation; when "false", only log it.

Outputs

  • {{ outputs.audit_page_for_tracking_cookies.scriptResults.tracking_check }}: list of tracked cookie names found in document.cookie immediately after navigation.
  • {{ outputs.audit_page_for_tracking_cookies.extracted.banner_matches }}: list of text content from elements matching consent_banner_selector; empty when no banner was found.
  • {{ outputs.classify_tracking_compliance.value }}: classification string, one of NO_TRACKING_DETECTED, PRE_CONSENT_TRACKING_DETECTED, PROBE_FAILED.
  • {{ outputs.status }}: flow-level alias of the same classification.

Pitfalls

  • This probe reads document.cookie immediately after NAVIGATE returns. Trackers that fire after a delay, on scroll, or on another async event will not be caught by this single immediate check; this blueprint intentionally checks the worst, most common case (cookies set at load time) and documents this limit rather than claiming full coverage.
  • document.cookie only exposes cookies that are not marked HttpOnly; an HttpOnly tracking cookie set by a first-party response header would not appear here even if it violates the same consent rule. This is a browser platform limitation, not a plugin limitation.
  • The shipped consent_banner_selector default is a generic guess across common banner markup patterns; verify it actually matches your site's real banner before trusting a NO_TRACKING_DETECTED combined with an empty banner_matches list, that combination could just as easily mean the selector is wrong as mean there is no banner.
  • EXTRACT_TEXT is used with multiple: true specifically so a missing banner does not fail the whole task; without multiple: true, a non-matching selector throws and the run would always classify as PROBE_FAILED instead of distinguishing a missing banner from a real probe failure.
  • This is a point-in-time synthetic check from one Grid location; it does not catch geotargeted consent logic that only misbehaves for visitors from specific regions.

Local Container Setup

To start a local Selenium Grid for testing, execute:

docker run -d --name kestra-selenium -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-chrome

Once running, the Grid UI is reachable at http://localhost:4444; use this same address as SELENIUM_GRID_URL.

If Kestra is running inside a Docker network, connect the Grid container to Kestra's network:

docker network connect YOUR_KESTRA_NETWORK kestra-selenium

(Run docker network ls to verify your active Kestra network name.)

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.