Spaces:
Running
Running
Download index.html from getreadystack/consent-audit-eu: direct link, hf CLI and curl.
- Browser
- Download file 42.4 kB
-
https://huggingface.co/spaces/getreadystack/consent-audit-eu/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/consent-audit-eu/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/consent-audit-eu/resolve/main/index.html
42.4 kB
| <html lang="en"><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Pre-Consent Tracker Audit for EU Client Websites</title> | |
| <meta name="description" content="Finds the analytics, pixels, fonts and embeds that fire before your cookie banner is answered - in the source, before you deploy. 24 rules."> | |
| <style> | |
| :root{color-scheme:light dark} | |
| body{margin:0;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif; | |
| background:#fbfbfa;color:#16161d} | |
| @media(prefers-color-scheme:dark){body{background:#14141a;color:#e8e8ea}} | |
| .wrap{max-width:880px;margin:0 auto;padding:28px 20px 64px} | |
| h1{font-size:1.7rem;line-height:1.25;margin:.2em 0 .3em} | |
| p.sub{margin:0 0 1.4em;opacity:.8} | |
| textarea{width:100%;min-height:230px;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace; | |
| padding:12px;border:1px solid #c9c9d1;border-radius:10px;background:transparent;color:inherit; | |
| box-sizing:border-box} | |
| button{font:600 15px/1 inherit;padding:12px 22px;border:0;border-radius:10px; | |
| background:#1d4ed8;color:#fff;cursor:pointer;margin:12px 0} | |
| button:hover{background:#1a43bd} | |
| table{border-collapse:collapse;width:100%;margin-top:14px;font-size:14px} | |
| th,td{border-bottom:1px solid #d8d8e0;padding:8px 6px;text-align:left;vertical-align:top} | |
| th{font-weight:600;opacity:.75} | |
| td.n{width:64px;font:13px ui-monospace,monospace;opacity:.7} | |
| .ok{padding:14px;border-radius:10px;background:#e8f5ec;color:#14532d;margin-top:14px} | |
| @media(prefers-color-scheme:dark){.ok{background:#15321f;color:#bbf7d0}} | |
| .next{margin-top:30px;padding:18px;border:1px solid #d8d8e0;border-radius:12px} | |
| .next h2{font-size:1.05rem;margin:0 0 .5em} | |
| .next ul{margin:.3em 0 1em 1.1em;padding:0} | |
| a.buy{display:inline-block;font:600 15px/1 inherit;padding:13px 22px;border-radius:10px;background:#1d4ed8;color:#fff;text-decoration:none} | |
| a.buy:hover{background:#1a43bd} | |
| p.till{font-size:13.5px;opacity:.8;margin:.9em 0 .2em} | |
| </style> | |
| <div class="wrap"> | |
| <h1>Pre-Consent Tracker Audit for EU Client Websites</h1> | |
| <p class="sub">Finds the analytics, pixels, fonts and embeds that fire before your cookie banner is answered - in the source, before you deploy. 24 rules.</p> | |
| <textarea id="src" placeholder="Paste an HTML, JS, JSX, Vue or PHP template here"></textarea> | |
| <button id="go">Audit this code</button> | |
| <div id="out"></div> | |
| <div class="next"><h2>When one clean file is not the job any more</h2> | |
| <p>Sweep every file in the client repository in one pass, keep it clean on every save, write the dated CSV/JSON/HTML evidence report you hand to the client or the DPO, fail the CI build on a new violation, and apply the mechanical fixes in place.</p> | |
| <ul><li>Audit the whole client repository</li><li>Dated evidence report (CSV, JSON or HTML)</li><li>Re-audit on every save</li><li>Machine-readable output for CI</li><li>Apply the mechanical fixes</li></ul> | |
| <p class="till">$29 once · one licence key per person or team seat · Consent scanners bill $10–$55 per domain <em>every month</em>, and only see the page after it is live.</p> | |
| <p><a class="buy" href="https://getreadystack.com/api/buy/cl/polar_cl_GLS2NunfF1vyIe7l2lyyErVSUzDSWPxuCzqQg13lMym">Get the full version — $29</a></p> | |
| </div> | |
| </div> | |
| <script> | |
| var RULES=[{"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*googletagmanager\\.com)", "flags": "i", "message": "Google Tag Manager loads on page render, so it runs before the visitor answers the banner. Every tag inside the container inherits that timing.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"statistics\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"statistics\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*connect\\.facebook\\.net)", "flags": "i", "message": "The Meta pixel loads on page render, so it runs before the visitor answers the banner. It writes _fbp and sends the visitor IP to Meta.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"marketing\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"marketing\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*static\\.hotjar\\.com)", "flags": "i", "message": "Hotjar loads on page render, so it runs before the visitor answers the banner. Session replay records the visitor from the first frame.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"statistics\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"statistics\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*clarity\\.ms)", "flags": "i", "message": "Microsoft Clarity loads on page render, so it runs before the visitor answers the banner. Clarity replays sessions and needs consent before the script starts.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"statistics\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"statistics\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*snap\\.licdn\\.com)", "flags": "i", "message": "The LinkedIn Insight Tag loads on page render, so it runs before the visitor answers the banner. It drops li_sugr and li_fat_id on load.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"marketing\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"marketing\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*cdn\\.segment\\.com)", "flags": "i", "message": "Segment analytics.js loads on page render, so it runs before the visitor answers the banner. It fans the visitor out to every destination you have enabled.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"statistics\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"statistics\"$2"}, {"pattern": "(<\\s*script)(?![^>]*text/plain)([^>]*src=[\"\\'][^\"\\']*(?:googleadservices\\.com|doubleclick\\.net))", "flags": "i", "message": "A Google Ads tag loads on page render, so it runs before the visitor answers the banner. Conversion and remarketing cookies are never strictly necessary.", "sev": "error", "fix": "Block it until consent: add type=\"text/plain\" data-cookieconsent=\"marketing\" so your CMP unblocks it after opt-in, or inject the tag from the consent callback.", "replace": "$1 type=\"text/plain\" data-cookieconsent=\"marketing\"$2"}, {"pattern": "gtag\\(\\s*[\"\\']config[\"\\']\\s*,\\s*[\"\\']G-", "flags": "i", "message": "gtag(\"config\",\"G-...\") sends a page_view to Google Analytics the moment this line runs. That is the pre-consent hit regulators quote in cookie decisions.", "sev": "error", "fix": "Move the config call inside the callback that runs after analytics consent is granted."}, {"pattern": "fbq\\(\\s*[\"\\']init[\"\\']", "flags": "i", "message": "fbq(\"init\", ...) starts Meta tracking on this line, ahead of any consent check.", "sev": "error", "fix": "Call fbq(\"init\") only after the marketing category is granted."}, {"pattern": "ttq\\.load\\(", "flags": "i", "message": "ttq.load() starts the TikTok pixel here, before the banner is answered.", "sev": "error", "fix": "Load the TikTok pixel from the consent callback."}, {"pattern": "[\"\\']clarity[\"\\']\\s*,\\s*[\"\\']script[\"\\']", "flags": "i", "message": "This is the inline Microsoft Clarity loader. It injects the replay script itself, so a CMP that only blocks external tags will miss it.", "sev": "error", "fix": "Run the loader from the consent callback instead of on page load."}, {"pattern": "(consent[\"\\']\\s*,\\s*[\"\\']default[\"\\'][^\\n]*?)granted", "flags": "i", "message": "Google Consent Mode defaults to \"granted\". In the EU the default must be \"denied\" until the visitor opts in, otherwise consent mode changes nothing.", "sev": "error", "fix": "Set the storage keys to \"denied\" in the default call and grant them in the update call.", "replace": "$1denied"}, {"pattern": "(?:ad_storage|analytics_storage|ad_user_data|ad_personalization)[\"\\']?\\s*:\\s*[\"\\']granted", "flags": "i", "message": "A consent-mode storage key is set to \"granted\" here. Check that this is the update after opt-in and not the default state.", "sev": "warn", "fix": "Keep every storage key \"denied\" in the default call."}, {"pattern": "<link[^>]+href=[\"\\'][^\"\\']*fonts\\.(?:googleapis|gstatic)\\.com", "flags": "i", "message": "Google Fonts is pulled from Google servers. The Munich court held that handing the visitor IP to Google this way is not strictly necessary, so it needs consent - or self-hosting.", "sev": "error", "fix": "Download the woff2 files and serve them from your own domain."}, {"pattern": "@import[^\\n]*fonts\\.googleapis\\.com", "flags": "i", "message": "A stylesheet @imports Google Fonts, which reaches Google before any banner renders.", "sev": "error", "fix": "Self-host the font files and drop the @import."}, {"pattern": "(<iframe[^>]*src=[\"\\'][^\"\\']*)(?:www\\.)?youtube\\.com/embed", "flags": "i", "message": "A youtube.com/embed iframe sets Google advertising cookies as soon as the page renders. The youtube-nocookie host does not.", "sev": "warn", "fix": "Swap the host to www.youtube-nocookie.com.", "replace": "$1www.youtube-nocookie.com/embed"}, {"pattern": "player\\.vimeo\\.com/video/\\d+(?![^\"\\'\\s]*dnt=1)", "flags": "i", "message": "This Vimeo embed carries no dnt=1, so Vimeo tracks the visitor on load.", "sev": "warn", "fix": "Append dnt=1 to the embed URL."}, {"pattern": "(?:maps\\.googleapis\\.com|google\\.com/maps/embed)", "flags": "i", "message": "A Google Maps embed sends the visitor IP to Google on load. Supervisory authorities treat that as a transfer that needs consent.", "sev": "warn", "fix": "Show a static image and load the real map on click, after consent."}, {"pattern": "www\\.google\\.com/recaptcha/api\\.js", "flags": "i", "message": "reCAPTCHA loads Google code on every page it sits on and profiles the visitor, not just the form submission.", "sev": "warn", "fix": "Load it when the form is focused, and write down why it is strictly necessary."}, {"pattern": "(?:widget\\.intercom\\.io|client\\.crisp\\.chat|embed\\.tawk\\.to)", "flags": "i", "message": "A chat widget pulls third-party code and cookies on page load. Unless the visitor asked for the chat, it waits for consent.", "sev": "warn", "fix": "Load the widget on click, from the consent callback."}, {"pattern": "(?:browser\\.sentry-cdn\\.com|datadoghq-browser-agent|sessionReplaySampleRate|replaysSessionSampleRate)", "flags": "i", "message": "Session replay is switched on. Replay records what the visitor types and points at, which needs consent before the SDK starts.", "sev": "warn", "fix": "Keep the replay sample rate at 0 until the statistics category is granted."}, {"pattern": "document\\.cookie\\s*=", "flags": "i", "message": "This line writes a cookie directly. Anything that is not strictly necessary to deliver the page has to wait for consent, and the choice has to be recorded.", "sev": "warn", "fix": "Wrap the write in your consent check, or drop the cookie."}, {"pattern": "(?:localStorage|sessionStorage)\\.setItem\\(\\s*[\"\\'][^\"\\']*(?:_ga|_gid|utm|visitor|analytics|track|fbp)", "flags": "i", "message": "Storing an identifier in web storage is access to terminal equipment under ePrivacy, exactly like a cookie, so it needs the same consent.", "sev": "warn", "fix": "Write the identifier only after the visitor opts in."}, {"pattern": "navigator\\.sendBeacon\\(", "flags": "i", "message": "sendBeacon posts data without waiting for a response. Check that this call cannot run before the consent state is known.", "sev": "info", "fix": "Gate the call on the stored consent state."}]; | |
| function scan(t){var L=String(t).split(/\r?\n/),h=[],i,r,re; | |
| for(i=0;i<L.length;i++){for(var k=0;k<RULES.length;k++){r=RULES[k]; | |
| try{re=new RegExp(r.pattern,r.flags||'')}catch(e){continue} | |
| if(re.test(L[i]))h.push({line:i+1,msg:r.message})}} | |
| return h} | |
| document.getElementById('go').onclick=function(){ | |
| var t=document.getElementById('src').value,o=document.getElementById('out'); | |
| if(!t.trim()){o.innerHTML='';return} | |
| var h=scan(t); | |
| if(!h.length){o.innerHTML='<div class="ok">Nothing fires before consent here</div>';return} | |
| var s='<table><tr><th>Line</th><th>Finding</th></tr>'; | |
| for(var i=0;i<h.length;i++){s+='<tr><td class="n">'+h[i].line+'</td><td>'+ | |
| String(h[i].msg).replace(/&/g,'&').replace(/</g,'<')+'</td></tr>'} | |
| o.innerHTML=s+'</table>'}; | |
| </script> | |
| </html> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://www.npmjs.com/package/@readystack/consent-audit-eu" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-audit-eu',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/consent-audit-eu" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-audit-eu',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/consent-audit-eu" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-audit-eu',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><a href="https://apify.com/readystack/seo-compliance-audit" rel="noopener" data-yj-install-door="apify" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-audit-eu',src:'apify',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Apify (audit live URLs)</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/consent-audit-eu <file> | |
| npx @readystack/consent-audit-eu --mcp | |
| docker run --rm -v "$PWD:/w" getreadystack/consent-audit-eu /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 24 rules Pre-Consent Tracker Audit for EU Client Websites checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"consent-audit-eu",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Finds the analytics, pixels, fonts and embeds that fire before your cookie banner is answered - in the source, before you deploy. 24 rules.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Consent scanners are billed per domain every month - CookieYes $10-$55 per domain per month, Cookiebot around EUR 30 since it doubled its base price in August 2025 - and they only see the page after it is live.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_GLS2NunfF1vyIe7l2lyyErVSUzDSWPxuCzqQg13lMym" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div> | |
| <script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-17">Updated 2026-09-17</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-17","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p>Five lines in this release workflow stop running on published dates - platform engineers who own <code>.github/workflows</code>, the first date 2026-09-23. Here is the audit of that file, exactly as it prints:</p> | |
| <pre><code>.github/workflows/release.yml | |
| 7 error the ubuntu-20.04 runner image became fully unsupported on 2025-04-15 - the job fails to start. Use ubuntu-24.04. | |
| 9 warn actions/checkout@v1-v4 runs on Node 20. Node 20 is removed from GitHub-hosted runners on 2026-09-23 - move to actions/checkout@v5 (needs runner 2.327.1 or later). | |
| 11 error the ::set-output workflow command was disabled on 2023-06-01 and is now ignored - write to the $GITHUB_OUTPUT file instead. | |
| 12 error artifact actions v3 were shut down on 2025-01-30 - the step fails today. Move to v6 (v4 changed to immutable artifacts, so a job that uploaded the same name twice needs merge-multiple). | |
| 14 warn ubuntu-22.04 and ubuntu-22.04-arm enter deprecation on 2026-09-17 and are fully unsupported on 2027-04-17 - move to ubuntu-24.04 or ubuntu-26.04 before the brownouts start.</code></pre> | |
| <p>Three of those five are already fatal. This is the file they came from:</p> | |
| <pre><code>name: release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| jobs: | |
| build: | |
| runs-on: ubuntu-20.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - run: npm ci && npm run build | |
| - run: echo "::set-output name=version::1.4.0" | |
| - uses: actions/upload-artifact@v3 | |
| smoke: | |
| runs-on: ubuntu-22.04</code></pre> | |
| <p>Nothing looks wrong, and the last run was green. A workflow doesn't rot gradually - it works until a morning when it doesn't start at all.</p> | |
| <p>One more date sits outside the file. Node 24 became the runner default on 2026-06-16, and the <code>ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true</code> opt-out that pins actions back to Node 20 dies on 2026-09-23 too, so it buys nothing.</p> | |
| <h3>Why the usual tools miss this</h3> | |
| <p>Dependabot and Renovate bump a <code>uses:</code> version when a new release appears - a different question from "when does this stop running". They never read <code>runs-on:</code> labels, so <code>ubuntu-22.04</code> and <code>macos-13</code> stay invisible, nor <code>::set-output</code>, nor <code>using: 'node20'</code> in a composite action of your own. And they can't tell you that <code>upload-artifact@v5</code> still defaults to Node 20 while v6 doesn't - that isn't in a version number, it's in a changelog entry.</p> | |
| <p>Code assistants are worse, not better: their training data predates these dates, so they autocomplete <code>actions/checkout@v4</code> and <code>runs-on: ubuntu-22.04</code> into the file you're fixing.</p> | |
| <h3>What to do this week</h3> | |
| <p>Check each workflow against the dates, not the version numbers. Already unsupported, so fix today: <code>ubuntu-20.04</code> to <code>ubuntu-24.04</code>, <code>windows-2019</code> to <code>windows-2025</code>, <code>macos-13</code> to <code>macos-15</code>, <code>upload-artifact@v3</code> to <code>@v6</code>, <code>::set-output</code> to <code>$GITHUB_OUTPUT</code>.</p> | |
| <p>Dated, so fix before 2026-09-23: <code>checkout@v1-v4</code> and <code>setup-node@v1-v4</code> to <code>@v5</code>, <code>upload-artifact@v4-v5</code> to <code>@v6</code>, <code>cache@v3-v4</code> to <code>@v5</code>, <code>setup-python@v1-v5</code> to <code>@v6</code>. And <code>ubuntu-22.04</code> to <code>ubuntu-24.04</code> before the 2026-09-17 brownouts. The v5/v6 actions need Actions Runner 2.327.1 or later, which GitHub-hosted runners have.</p> | |
| <h3>Free, and what the licence buys</h3> | |
| <p>GitHub Actions Deprecation Lint is a VS Code extension carrying 25 of these dated rules, marked by line number in the file you have open. That tier is free, asks for no key and has no watermark - the block at the top of this article is exactly what it prints. The same 25 rules run on a web page too, nothing installed. Every date comes from GitHub's changelog and runner-images notes.</p> | |
| <p>The licence buys scope, not more rules - both tiers run the identical 25. Free checks the one file in front of you. The licence scans every workflow in the repository in one pass, including the composite <code>action.yml</code> files where <code>using: 'node20'</code> hides; exports CSV, JSON or HTML; prints JSON a CI step can fail on; re-checks on every save; and takes your own rules for internal actions and runner labels. $29 once, one key per person or team seat, 7-day full refund.</p></div><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Consent Audit EU actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads the source you are editing and flags every line that tracks a visitor before the cookie banner is answered: tag managers, pixels, session replay, Google Fonts, map and video embeds, consent-mode defaults left on granted, cookies and storage identifiers written on load. Each finding gives the line number, the reason and the fix. 24 rules ship inside.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Web agencies and freelance developers who build sites for clients in the EU, EEA or the UK, and who are the ones asked why a tag fired before the banner. It is written for the person editing the template, not for the lawyer reading the report, so every finding points at a line you can change today.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">I already installed a consent platform. Why is this different?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A consent platform serves the banner and scans the page after it is deployed. Most cookie fines cite tags firing before consent, not the banner wording, and that timing is decided in your source. This reads the repository you are editing and names the line, so the problem is gone before the page ships.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free finishes one file: open any template, audit it or a selection, read the report with line numbers and severity, and list every rule. Nothing is blurred, watermarked or counted down. The licence adds scale and proof: the whole repository in one pass, re-audit on save, CI output, the mechanical fixes, and the dated evidence report.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">How does the price compare?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Consent scanners are billed per domain every month: CookieYes runs $10 to $55 per domain per month and Cookiebot sits near EUR 30 after doubling its base price in August 2025, with no volume discount. This is $29 once, for every project you open,.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Consent Audit EU checks the file you open against 24 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Consent Audit EU actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads the source you are editing and flags every line that tracks a visitor before the cookie banner is answered: tag managers, pixels, session replay, Google Fonts, map and video embeds, consent-mode defaults left on granted, cookies and storage identifiers written on load. Each finding gives the line number, the reason and the fix. 24 rules ship inside."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Web agencies and freelance developers who build sites for clients in the EU, EEA or the UK, and who are the ones asked why a tag fired before the banner. It is written for the person editing the template, not for the lawyer reading the report, so every finding points at a line you can change today."}}, {"@type": "Question", "name": "I already installed a consent platform. Why is this different?", "acceptedAnswer": {"@type": "Answer", "text": "A consent platform serves the banner and scans the page after it is deployed. Most cookie fines cite tags firing before consent, not the banner wording, and that timing is decided in your source. This reads the repository you are editing and names the line, so the problem is gone before the page ships."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Free finishes one file: open any template, audit it or a selection, read the report with line numbers and severity, and list every rule. Nothing is blurred, watermarked or counted down. The licence adds scale and proof: the whole repository in one pass, re-audit on save, CI output, the mechanical fixes, and the dated evidence report."}}, {"@type": "Question", "name": "How does the price compare?", "acceptedAnswer": {"@type": "Answer", "text": "Consent scanners are billed per domain every month: CookieYes runs $10 to $55 per domain per month and Cookiebot sits near EUR 30 after doubling its base price in August 2025, with no volume discount. This is $29 once, for every project you open,."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Consent Audit EU checks the file you open against 24 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:13375,slug:"consent-audit-eu",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/pre-consent-cookie-audit-2026"><span class="yj-rel-t">Cookie Pre-Consent Audit 2026 — France, Spain, Italy</span><span class="yj-rel-p">$60</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/portfolio-ui-kit-gdpr-embeds"><span class="yj-rel-t">Portfolio UI Kit — GDPR Two-Click Embeds & Consent Banner (Figma)</span><span class="yj-rel-p">$168</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/saas-ui-kit-gdpr-consent"><span class="yj-rel-t">SaaS UI Kit — GDPR Consent Components for Figma (Cookie Banner, Opt-in, Withdr</span><span class="yj-rel-p">$168</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/billable-hours-tracker-firefox"><span class="yj-rel-t">Billable Tab Timer for Hourly Freelancers & Consultants — Firefox extension th</span><span class="yj-rel-p">$9</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/portfolio-ui-kit-gdpr-cookie-banner"><span class="yj-rel-t">Portfolio UI Kit for EU Freelancers: GDPR Contact Form, Cookie Banner by Regio</span><span class="yj-rel-p">$168</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/n8n-social-media-workflow-agency-content-approval"><span class="yj-rel-t">n8n Social Media Workflow for Agency Content Approval — 6 Client Sign-Off Auto</span><span class="yj-rel-p">$59</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section> |