<?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: Spicy</title>
    <description>The latest articles on DEV Community by Spicy (@spicykim).</description>
    <link>https://dev.to/spicykim</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%2F3930300%2Ff80e2e97-ebe7-4ee3-b2cb-d70ff6eac7bc.png</url>
      <title>DEV Community: Spicy</title>
      <link>https://dev.to/spicykim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spicykim"/>
    <language>en</language>
    <item>
      <title>Your Baby Monitor's Biggest Security Flaw Isn't Hackers. It's the Company That Built It.</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 25 Jun 2026 16:50:56 +0000</pubDate>
      <link>https://dev.to/spicykim/your-baby-monitors-biggest-security-flaw-isnt-hackers-its-the-company-that-built-it-5hbj</link>
      <guid>https://dev.to/spicykim/your-baby-monitors-biggest-security-flaw-isnt-hackers-its-the-company-that-built-it-5hbj</guid>
      <description>&lt;p&gt;In May 2026, a French ethical hacker named Sammy Azdoufal bought a baby monitor off Amazon and spent a few hours looking at its network traffic. What he found: 1.1 million cameras across 300+ brand names, all running on the same shared platform, accessible to anyone with a free account. No password cracking. No exploit chain. He clicked a URL and got the image.&lt;/p&gt;

&lt;p&gt;The vulnerability wasn't a clever attack. It was negligence — hardcoded credentials, an MQTT broker with no per-device access controls, and motion-alert images sitting on an Alibaba OSS bucket with no authentication required.&lt;/p&gt;

&lt;p&gt;This is the actual baby monitor security problem. Not a stranger breaking in through your Wi-Fi. The architecture itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The White-Label Problem
&lt;/h2&gt;

&lt;p&gt;Most budget smart cameras on Amazon are the same product under different names.&lt;/p&gt;

&lt;p&gt;Meari Technology (Hangzhou, China) supplies hardware, software, and cloud infrastructure to 300+ brands. When you buy a monitor you've never heard of — or even some you have — there's a real chance it shares a backend platform with hundreds of other products. The box doesn't disclose which cloud it connects to. The app is often interchangeable across brands.&lt;/p&gt;

&lt;p&gt;A flaw at the platform level means millions of devices are exposed simultaneously, regardless of brand name.&lt;/p&gt;

&lt;p&gt;Rapid7 tested nine popular baby monitors and gave eight of them an "F" for security. Higher prices didn't correlate with better security — more features meant more attack surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Encrypted" Actually Means Here
&lt;/h2&gt;

&lt;p&gt;Many monitors market end-to-end encryption. Most don't implement it correctly.&lt;/p&gt;

&lt;p&gt;Real E2E encryption: only your device and your app can decrypt the stream — not the manufacturer's servers.&lt;/p&gt;

&lt;p&gt;What most Wi-Fi monitors actually do: encrypt the leg from your camera to their servers, and the leg from their servers to your app. The manufacturer sees plaintext at the server level.&lt;/p&gt;

&lt;p&gt;The Meari MQTT broker (CVE-2026-33356) let any authenticated platform account subscribe to camera activity across all devices on a regional broker. Azdoufal observed 2,000+ device messages within minutes from a single broker endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Reduces Risk
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Network segmentation&lt;/strong&gt; — most underused, highest impact. Put your baby monitor on a guest Wi-Fi VLAN, isolated from your main devices. A compromised monitor can't pivot to your laptop or NAS. Takes 5 minutes on most home routers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firmware updates over passwords&lt;/strong&gt; — a strong password won't protect you if the backend has an unpatched CVE. Enable auto-updates or check monthly. This matters more than password complexity on a vulnerable platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brand selection criteria that actually signal security:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Published vulnerability disclosure program or bug bounty&lt;/li&gt;
&lt;li&gt;Documented patch history (not just "we take security seriously")&lt;/li&gt;
&lt;li&gt;Privacy policy that specifies data collected during idle operation — not just when you're actively viewing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What doesn't move the needle as much:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong passwords alone on a platform-level vulnerability&lt;/li&gt;
&lt;li&gt;"No cloud" marketing that still phones home for firmware and analytics&lt;/li&gt;
&lt;li&gt;Price as a proxy for security&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Regulatory Gap
&lt;/h2&gt;

&lt;p&gt;The FCC's US Cyber Trust Mark — a voluntary IoT security labeling program — is still in development. "Good cybersecurity is invisible to consumers. They can't tell what products are risky," Stacey Higginbotham of Consumer Reports said following the Meari disclosure.&lt;/p&gt;

&lt;p&gt;Until mandatory baseline security requirements exist for IoT devices sold in the US, the research burden falls on buyers. Consumer Reports now runs security and privacy evaluations on baby monitors specifically — worth checking before purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Monitor is on a dedicated guest/IoT VLAN
[ ] Firmware auto-updates enabled (or manual check set monthly)
[ ] Default credentials changed immediately on setup
[ ] Brand has published vulnerability disclosure program
[ ] Privacy policy reviewed for third-party data sharing
[ ] App permissions audited (disable anything not needed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Meari disclosure made the problem concrete at a scale that's hard to ignore. 1.1 million cameras. Five critical CVEs. Footage of children's bedrooms accessible to anyone with a free account and a few minutes.&lt;/p&gt;

&lt;p&gt;That's not a hacking story. It's a product design story.&lt;/p&gt;




&lt;p&gt;Full breakdown: &lt;a href="https://lucas8.com/baby-monitor-privacy-risks" rel="noopener noreferrer"&gt;https://lucas8.com/baby-monitor-privacy-risks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Two-Factor Auth Isn't the Shield You Think It Is.</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:58:28 +0000</pubDate>
      <link>https://dev.to/spicykim/two-factor-auth-isnt-the-shield-you-think-it-is-5c77</link>
      <guid>https://dev.to/spicykim/two-factor-auth-isnt-the-shield-you-think-it-is-5c77</guid>
      <description>&lt;p&gt;You enabled two-factor authentication. Good call. But here's what most security guides skip: the biggest account takeovers of the past three years didn't require attackers to crack your 2FA code at all. They found ways to make you hand it over — or made the authentication step irrelevant entirely.&lt;/p&gt;

&lt;p&gt;MFA fatigue attacks are now one of the most documented techniques in real breaches, and they work precisely because 2FA gave everyone a false sense of being done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 2FA Still Gets Beaten in 2026
&lt;/h2&gt;

&lt;p&gt;Traditional 2FA was designed to stop credential stuffing — someone stealing your password and trying to log in. For that specific threat, it works brilliantly. Microsoft's data shows MFA blocks over 99% of automated credential attacks.&lt;/p&gt;

&lt;p&gt;The problem is the threat didn't stay still. Attackers noticed 2FA made the password less valuable, so they shifted focus: steal the session, exhaust the user, or reroute the auth flow entirely. These aren't exotic techniques. They show up in the Verizon DBIR every year, and the companies hit aren't careless ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Techniques Hackers Use to Walk Past MFA
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. MFA Fatigue (Push Bombing)
&lt;/h3&gt;

&lt;p&gt;An attacker who has your username and password triggers your push notification repeatedly — sometimes dozens of times overnight. Most people eventually tap "Approve" to make it stop. That single tap hands over a valid session token.&lt;/p&gt;

&lt;p&gt;This exact method took down Uber in 2022. The attacker bought leaked credentials, bombed the contractor's phone with push requests, then sent a WhatsApp message posing as Uber IT saying "approve once and it'll stop." The contractor did. Game over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Enable number matching on push apps. You type a two-digit code shown on your screen into the push prompt — blind approvals stop working immediately. Microsoft Authenticator and Duo both support this and Microsoft enabled it by default across Entra ID in 2023.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Adversary-in-the-Middle (AiTM) Attacks
&lt;/h3&gt;

&lt;p&gt;An attacker sets up a proxy that looks identical to a real login page. When you log in through it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The proxy relays your credentials to the real site&lt;/li&gt;
&lt;li&gt;The real site sends back a 2FA challenge — the proxy relays that too&lt;/li&gt;
&lt;li&gt;You enter your code — the proxy captures your authenticated &lt;strong&gt;session cookie&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The attacker replays that cookie from a clean browser, no further auth required
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You → [Fake Proxy] → Real Site
             ↓
      Session Cookie stolen
             ↓
Attacker → [Replays Cookie] → Logged In
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The session cookie is the key. Your 2FA was used — legitimately — to create a session the attacker now owns. The Verizon 2025 DBIR flags stolen session tokens as a growing proportion of breach vectors for exactly this reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Only passkeys and hardware security keys are immune to this. They use cryptographic proofs tied to the exact origin domain — a proxy gets a different challenge and the login fails silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SIM Swapping
&lt;/h3&gt;

&lt;p&gt;An attacker calls your carrier posing as you — armed with info from data brokers or social media — and convinces a rep to transfer your number to a SIM they control. From that point, every SMS code sent to your phone goes to them instead.&lt;/p&gt;

&lt;p&gt;No malware. Nothing on your device. The FTC has received thousands of SIM swap reports annually since 2021.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Move off SMS 2FA entirely for anything financial or high-value. Authenticator apps (Google Authenticator, Aegis, Authy) generate codes locally on your device — a SIM swap can't intercept them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2FA Method Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Stops Credential Stuffing&lt;/th&gt;
&lt;th&gt;Stops Push Bombing&lt;/th&gt;
&lt;th&gt;Stops AiTM&lt;/th&gt;
&lt;th&gt;Stops SIM Swap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMS OTP&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authenticator App (TOTP)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push (standard)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push + Number Matching&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware Key / Passkey&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What "Phishing-Resistant" Actually Means
&lt;/h2&gt;

&lt;p&gt;Phishing-resistant MFA uses public-key cryptography where your device proves it's physically present at the &lt;strong&gt;real&lt;/strong&gt; domain. There's no code to intercept, no push to trick you into approving, no SMS to redirect.&lt;/p&gt;

&lt;p&gt;Both hardware security keys (YubiKey etc.) and passkeys qualify. FIDO Alliance reports 5 billion active passkeys worldwide as of 2026, and 68% of organizations surveyed are actively deploying them for employee sign-in. Google, Apple, and Microsoft have all made passkeys the default for new accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Upgrade Path
&lt;/h2&gt;

&lt;p&gt;You don't need to switch everything at once:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This week:&lt;/strong&gt; Remove SMS 2FA from any financial account. Switch to an authenticator app — takes ~30 seconds per account in settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This month:&lt;/strong&gt; Enable number matching on any push-based MFA app. Check your organization's Entra ID or Okta settings to confirm it's on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When ready:&lt;/strong&gt; Add a hardware key ($25–55 for a basic YubiKey) for email, password manager, and any admin access. Set up passkeys on Google, Apple, and GitHub — each takes under three minutes.&lt;/p&gt;




&lt;p&gt;The goal isn't to scare you off 2FA — it's to make sure what you're running matches the current threat, not the threat from 2019. Getting off SMS and onto an authenticator app this week already moves you out of the most exploited tier.&lt;/p&gt;

&lt;p&gt;Full breakdown with real breach examples: &lt;a href="https://lucas8.com/mfa-fatigue-attack-two-factor-bypass" rel="noopener noreferrer"&gt;https://lucas8.com/mfa-fatigue-attack-two-factor-bypass&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Browser Fingerprinting in Practice — The Signals, the Math, and What Actually Defeats It</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 18 Jun 2026 16:38:31 +0000</pubDate>
      <link>https://dev.to/spicykim/browser-fingerprinting-in-practice-the-signals-the-math-and-what-actually-defeats-it-15k0</link>
      <guid>https://dev.to/spicykim/browser-fingerprinting-in-practice-the-signals-the-math-and-what-actually-defeats-it-15k0</guid>
      <description>&lt;p&gt;Most privacy advice still centers on cookies — clear them, block them, use incognito. Meanwhile, fingerprinting has become the dominant tracking method precisely because it doesn't touch cookies at all. Here's what's actually happening at the API level, and what countermeasures hold up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Signals (And the Code Behind Them)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Canvas fingerprinting&lt;/strong&gt; exploits subtle rendering differences between GPU/driver combinations:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCanvasFingerprint&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;canvas&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;canvas&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Draw text with specific font, size, and emoji — &lt;/span&gt;
  &lt;span class="c1"&gt;// rendering varies by OS font rasterizer and GPU&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textBaseline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;14px Arial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f60&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#069&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cwm fjordbank glyphs vext quiz 🎮&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(102, 204, 0, 0.7)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cwm fjordbank glyphs vext quiz 🎮&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Hash the resulting pixel data&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Hash the output for compact comparison&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashFingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataUrl&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;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataUrl&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;hashBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashBuffer&lt;/span&gt;&lt;span class="p"&gt;))&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same code produces different pixel-level output across GPU vendors (NVIDIA vs AMD vs Apple Silicon), driver versions, and even font hinting settings — none of which the user can see, but all of which produce a consistent hash per device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebGL fingerprinting&lt;/strong&gt; goes deeper into hardware identification:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWebGLFingerprint&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;canvas&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;canvas&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;gl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webgl&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;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;experimental-webgl&lt;/span&gt;&lt;span class="dl"&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;gl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;debugInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WEBGL_debug_renderer_info&lt;/span&gt;&lt;span class="dl"&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;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debugInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNMASKED_VENDOR_WEBGL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debugInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNMASKED_RENDERER_WEBGL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// e.g. "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11..."&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSupportedExtensions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;maxTextureSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_TEXTURE_SIZE&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;This often reveals your exact GPU model, which on its own significantly narrows the population of matching devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AudioContext fingerprinting&lt;/strong&gt; uses hardware-dependent audio processing variance:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAudioFingerprint&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;audioCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new &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;AudioContext&lt;/span&gt; &lt;span class="o"&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;webkitAudioContext&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;oscillator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOscillator&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;analyser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAnalyser&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;gainNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;gainNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// silent — user hears nothing&lt;/span&gt;
  &lt;span class="nx"&gt;oscillator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;triangle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;oscillator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;analyser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gainNode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;gainNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;oscillator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frequencyBinCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;analyser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFloatFrequencyData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;oscillator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// sample of frequency data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Font enumeration&lt;/strong&gt; via measurement comparison (no direct font list API exists, so it's inferred):&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectFonts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testFonts&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;baseFonts&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;monospace&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;sans-serif&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;serif&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;testString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mmmmmmmmmmlli&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;testSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;72px&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;span&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;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;span&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;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;span&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="nx"&gt;testString&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;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;span&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;baseWidths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nx"&gt;baseFonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;span&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;fontFamily&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;baseWidths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&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;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testFonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;baseFonts&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;base&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;span&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;fontFamily&lt;/span&gt; &lt;span class="o"&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;font&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;base&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;baseWidths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;base&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="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;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;detected&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;h2&gt;
  
  
  Composite Scoring — How These Combine
&lt;/h2&gt;

&lt;p&gt;A single signal rarely identifies anyone uniquely. The entropy comes from combining them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Shannon entropy in bits — higher = more identifying
    e.g. if 1 in 1000 users share your exact value, 
    that signal contributes ~10 bits of entropy
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;entropy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signal_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
        &lt;span class="n"&gt;entropy&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;

&lt;span class="c1"&gt;# Example combined fingerprint entropy
&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;screen_resolution&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# bits
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;canvas_hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;webgl_renderer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;6.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;font_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timezone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;audio_fingerprint&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;total_entropy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# ~32.3 bits
# 2^32.3 ≈ 5.3 billion possible combinations
# Global population ~8 billion — this fingerprint 
# alone approaches unique identification
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why the &lt;a href="https://coveryourtracks.eff.org/" rel="noopener noreferrer"&gt;EFF Panopticlick research&lt;/a&gt; consistently found most tested browsers had fingerprints unique among hundreds of thousands of samples — the combinatorial entropy adds up fast even when no individual signal is rare.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Reduces Entropy (Tested)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Firefox &lt;code&gt;privacy.resistFingerprinting&lt;/code&gt;&lt;/strong&gt; standardizes the highest-entropy signals:&lt;br&gt;
Testing before/after on the same machine with &lt;a href="https://amiunique.org/" rel="noopener noreferrer"&gt;amiunique.org&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Default Firefox&lt;/th&gt;
&lt;th&gt;resistFingerprinting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Canvas&lt;/td&gt;
&lt;td&gt;Unique hash&lt;/td&gt;
&lt;td&gt;Blocked/randomized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timezone&lt;/td&gt;
&lt;td&gt;Local (e.g. PST)&lt;/td&gt;
&lt;td&gt;UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen resolution&lt;/td&gt;
&lt;td&gt;Exact (e.g. 1512x982)&lt;/td&gt;
&lt;td&gt;Rounded (1500x950)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fonts detected&lt;/td&gt;
&lt;td&gt;40+ system fonts&lt;/td&gt;
&lt;td&gt;~12 bundled fonts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebGL renderer&lt;/td&gt;
&lt;td&gt;Full GPU string&lt;/td&gt;
&lt;td&gt;Generic string&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tradeoff: sites relying on accurate viewport dimensions for layout can render incorrectly, and some canvas-dependent web apps (image editors, games) break entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brave's fingerprinting protection&lt;/strong&gt; takes a different approach — randomizing per-session rather than blocking:&lt;br&gt;
&lt;strong&gt;Tor Browser&lt;/strong&gt; standardizes nearly everything to a single shared profile across all users, which is the only approach that achieves near-zero fingerprint uniqueness — at the cost of significant performance and compatibility overhead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Detection-Side: If You're Building Anti-Fraud Systems
&lt;/h2&gt;

&lt;p&gt;For legitimate use cases (fraud detection, not tracking users for ads), fingerprinting libraries like FingerprintJS provide production-ready implementations:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;FingerprintJS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fingerprintjs/fingerprintjs&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;fpPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FingerprintJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fpPromise&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;visitorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// stable identifier&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;confidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0-1 reliability&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worth noting: privacy-focused browsers and extensions specifically target known fingerprinting libraries, so production fraud detection systems increasingly see degraded signal quality from privacy-conscious users — which itself becomes a (weaker) signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Takeaway
&lt;/h2&gt;

&lt;p&gt;Cookie-based privacy controls (clearing cookies, incognito mode, cookie blockers) have zero effect on any of the above. If you're building privacy-respecting infrastructure or just hardening your own setup, the signals that matter are canvas, WebGL, audio context, and font enumeration — and the only consistently effective countermeasures are resistFingerprinting-style signal normalization or full standardization (Tor).&lt;/p&gt;

&lt;p&gt;Consumer-level explanation without the code: &lt;a href="https://lucas8.com/incognito-mode-browser-fingerprinting" rel="noopener noreferrer"&gt;lucas8.com/incognito-mode-browser-fingerprinting&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Data Brokers: What They Collect, How the Industry Works, and How to Opt Out at Scale</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Sun, 14 Jun 2026 15:16:43 +0000</pubDate>
      <link>https://dev.to/spicykim/data-brokers-what-they-collect-how-the-industry-works-and-how-to-opt-out-at-scale-4pg2</link>
      <guid>https://dev.to/spicykim/data-brokers-what-they-collect-how-the-industry-works-and-how-to-opt-out-at-scale-4pg2</guid>
      <description>&lt;p&gt;Most developers know abstractly that data brokers exist. Fewer have actually looked up their own profile and seen what's there — their home address, every previous address, relatives' names and addresses, income estimate, vehicle history, court records, and consumer interest categories.&lt;/p&gt;

&lt;p&gt;Here's how the data pipeline actually works, what a profile contains at the data level, and how to approach opt-outs at scale rather than one form at a time.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Data Broker Pipelines Work
&lt;/h2&gt;

&lt;p&gt;Data brokers aggregate from three primary source categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public records&lt;/strong&gt; — property records, voter registration, court filings, professional licenses, business registrations, marriage/divorce records, death records. These are legally public in the US and most other countries. Brokers ingest them continuously via bulk data agreements with county, state, and federal agencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial data&lt;/strong&gt; — purchase history from retailers (via loyalty programs and direct sales), subscription records, warranty registrations, financial transaction metadata (purchased from banks and credit card processors), insurance records, telecommunications data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party data&lt;/strong&gt; — scraped from social media and public web, purchased from other data brokers (the industry extensively resells to itself), purchased from app developers who include data-sharing SDKs.&lt;/p&gt;

&lt;p&gt;The aggregation logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified version of identity resolution logic
# (what brokers call "entity resolution" or "data matching")
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PersonProfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Match records across sources using probabilistic 
    identity resolution — name + address + DOB + phone
    combinations weighted by confidence score
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;clusters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;identity_match_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PersonCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&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="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;identity_match_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;fuzzy_name_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why a data broker profile contains people you've lived with — shared address history creates a probabilistic link that their systems treat as a relationship signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Actually in a Profile (Data Schema)
&lt;/h2&gt;

&lt;p&gt;A typical commercial data broker profile at the API level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"person"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"names"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane A. Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Adams"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dob_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1985-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1985-12-31"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phones"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"+15551234567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+15559876543"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"emails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jane@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jane.smith@oldwork.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123 Main St, Austin TX 78701"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"current"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2021-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"456 Oak Ave, Denver CO 80203"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"previous"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2021-02"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"associates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Robert Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"relationship"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"relative"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shared_addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"123 Main St, Austin TX 78701"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"financials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"income_estimate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"net_worth_estimate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"homeowner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"property_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;385000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"criminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"civil"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bankruptcies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"liens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumer_segments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"health_conscious_shopper"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"frequent_traveler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="s2"&gt;"suburban_homeowner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"political_donor_democrat"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;consumer_segments&lt;/code&gt; field is the advertising product — these interest/demographic categories are what marketers buy. The address and associate data is what stalkers, scammers, and PI firms buy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Opt-Out Landscape
&lt;/h2&gt;

&lt;p&gt;There are approximately 4,000 data brokers. Manually opting out of each one is not realistic. The practical approach is tiered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 — High-traffic consumer-facing sites (manual opt-out, highest priority)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Opt-Out URL&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spokeo&lt;/td&gt;
&lt;td&gt;spokeo.com/optout&lt;/td&gt;
&lt;td&gt;Email form&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WhitePages&lt;/td&gt;
&lt;td&gt;whitepages.com/suppression_requests&lt;/td&gt;
&lt;td&gt;Web form&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BeenVerified&lt;/td&gt;
&lt;td&gt;beenverified.com/opt-out&lt;/td&gt;
&lt;td&gt;Web form&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MyLife&lt;/td&gt;
&lt;td&gt;mylife.com&lt;/td&gt;
&lt;td&gt;Phone call required&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Radaris&lt;/td&gt;
&lt;td&gt;radaris.com/page/privacy&lt;/td&gt;
&lt;td&gt;Email form&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intelius&lt;/td&gt;
&lt;td&gt;intelius.com/optout&lt;/td&gt;
&lt;td&gt;Web form&lt;/td&gt;
&lt;td&gt;~3-6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TTL = time before listing typically reappears from re-aggregation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2 — Automated opt-out via paid services&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Services like DeleteMe, Incogni, and Privacy Bee submit opt-outs across 100–750 brokers and resubmit on a schedule. Worth the cost if you're doing this for yourself or building it into a product for users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3 — Enterprise data brokers (requires legal process)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Acxiom, LexisNexis, CoreLogic, Equifax (non-credit), TransUnion Marketing — these serve enterprise customers and have different opt-out mechanisms. Acxiom has an opt-out at aboutthedata.com. LexisNexis requires a written request with ID verification. California CCPA requests get the fastest response for these sources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automating Opt-Out Submissions
&lt;/h2&gt;

&lt;p&gt;For the manual tier, the process is repetitive and automatable for the sites that use web forms rather than email or phone:&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;// Playwright automation for form-based opt-outs&lt;/span&gt;
&lt;span class="c1"&gt;// (Shown for educational purposes — &lt;/span&gt;
&lt;span class="c1"&gt;//  check each site's ToS before automating)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitOptOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profileUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spokeo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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://www.spokeo.com/optout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#profile_url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profileUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radaris&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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://radaris.com/page/privacy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="email"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="url"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profileUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Rate limit to avoid triggering bot detection&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;batchOptOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&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;profile&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;submitOptOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3000&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 main friction points: CAPTCHA on some forms, email confirmation required on most, and a few sites require the user to find their own profile URL first (can't just submit a name).&lt;/p&gt;




&lt;h2&gt;
  
  
  CCPA as a Lever
&lt;/h2&gt;

&lt;p&gt;For California residents (and US residents targeting California-based brokers), the CCPA gives individuals the right to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know what data is collected about them&lt;/li&gt;
&lt;li&gt;Request deletion&lt;/li&gt;
&lt;li&gt;Opt out of sale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Submitting a CCPA deletion request often gets faster and more thorough responses than the standard opt-out form, even from brokers that theoretically don't have to respond. Use the Global Privacy Control (GPC) signal in your browser header — it's legally recognized in California and several other states:&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;// GPC header — supported by Firefox and Brave natively&lt;/span&gt;
&lt;span class="c1"&gt;// Can be set programmatically:&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;globalPrivacyControl&lt;/span&gt; &lt;span class="c1"&gt;// true if GPC enabled&lt;/span&gt;

&lt;span class="c1"&gt;// For server-side requests:&lt;/span&gt;
&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sec-GPC&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Realistic Picture
&lt;/h2&gt;

&lt;p&gt;Manual opt-outs from the top 10-15 consumer-facing sites takes about 2-3 hours and provides meaningful short-term privacy improvement, particularly for address exposure. The data comes back in 3-6 months.&lt;/p&gt;

&lt;p&gt;The deeper problem is that the legal architecture in the US makes this a whack-a-mole exercise until federal privacy legislation passes. For users who need durable protection — domestic violence survivors, public figures, journalists — the paid services plus CCPA requests plus synthetic identity strategies (PO boxes, registered agents for property) are the more serious toolkit.&lt;/p&gt;

&lt;p&gt;Consumer-facing explanation of the same topic, including the exact opt-out steps for each major site: &lt;a href="https://lucas8.com/data-broker-opt-out-guide" rel="noopener noreferrer"&gt;lucas8.com/data-broker-opt-out-guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How AI Phishing Emails Are Built (And the One Pattern That Always Gives Them Away)</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Sun, 14 Jun 2026 14:24:56 +0000</pubDate>
      <link>https://dev.to/spicykim/how-ai-phishing-emails-are-built-and-the-one-pattern-that-always-gives-them-away-2pnm</link>
      <guid>https://dev.to/spicykim/how-ai-phishing-emails-are-built-and-the-one-pattern-that-always-gives-them-away-2pnm</guid>
      <description>&lt;p&gt;Most phishing detection advice is now actively harmful. Teaching users to look for typos and generic greetings made sense when phishing was a spray-and-pray operation running on bad grammar. That era is over.&lt;/p&gt;

&lt;p&gt;Here's how modern AI phishing is actually constructed, what signals remain reliable, and how to implement detection logic that accounts for the current threat model.&lt;/p&gt;




&lt;h2&gt;
  
  
  How an AI Spear Phishing Email Gets Built
&lt;/h2&gt;

&lt;p&gt;The workflow an attacker follows in 2026 is largely automated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Target reconnaissance&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Typical OSINT data sources for a targeted attack
&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linkedin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job title, manager name, team structure, tenure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company_website&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email format (first.last@company.com), press releases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;social_media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent posts, projects mentioned, travel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_broker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;personal email, phone, home address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;previous_breaches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password patterns, security question answers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this is publicly available or purchasable. A targeted attack on a finance manager will include their correct name, their CFO's actual name, and may reference a real business event pulled from a press release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Prompt engineering for the attack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The attacker doesn't write the email. They prompt a model:&lt;br&gt;
The output is indistinguishable from a real internal email.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lookalike domains are registered with realistic names (&lt;code&gt;company-billing.com&lt;/code&gt;, &lt;code&gt;companyfinance.io&lt;/code&gt;), SSL certificates acquired (free via Let's Encrypt — the padlock means nothing), and emails sent through legitimate SMTP infrastructure to pass basic spam filters.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Traditional Detection Gets Wrong
&lt;/h2&gt;

&lt;p&gt;The signals security training still teaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Why It Fails Now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typos / bad grammar&lt;/td&gt;
&lt;td&gt;LLMs produce perfect prose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic greeting&lt;/td&gt;
&lt;td&gt;OSINT provides correct names trivially&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown sender&lt;/td&gt;
&lt;td&gt;Lookalike domains pass visual inspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suspicious links&lt;/td&gt;
&lt;td&gt;Links go to legitimate sites that redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urgency alone&lt;/td&gt;
&lt;td&gt;Legitimate emails also have urgency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are reliable discriminators in 2026.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Still Works: Authentication Layer Checks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SPF, DKIM, DMARC&lt;/strong&gt; — these operate at the email infrastructure level and can't be faked without compromising the legitimate domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check authentication results for a received email&lt;/span&gt;
&lt;span class="c"&gt;# Look for these headers in the raw message&lt;/span&gt;

&lt;span class="c"&gt;# SPF: did the email originate from an authorized server?&lt;/span&gt;
Received-SPF: pass &lt;span class="o"&gt;(&lt;/span&gt;google.com: domain of cfo@company.com designates 
  198.51.100.1 as permitted sender&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# DKIM: was the email cryptographically signed by the domain?&lt;/span&gt;
DKIM-Signature: &lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rsa-sha256&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;company.com&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;selector1&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# DMARC: does the domain's policy require both to pass?&lt;/span&gt;
Authentication-Results: mx.google.com&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;dkim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass header.d&lt;span class="o"&gt;=&lt;/span&gt;company.com&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;spf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass smtp.mailfrom&lt;span class="o"&gt;=&lt;/span&gt;company.com&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;dmarc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;REJECT&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A legitimate internal email from your CFO should pass all three. Any failure is a hard signal — not a soft one. Most attackers can't pass DMARC on the domain they're spoofing without compromising it directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Programmatic header parsing:&lt;/strong&gt;&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;// Parse authentication results from email headers&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseAuthResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&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;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authentication-results&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="dl"&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;spf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/spf=&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;pass|fail|softfail|neutral&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dkim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/dkim=&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;pass|fail|none&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dmarc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/dmarc=&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;pass|fail|none&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAuthenticationSuspicious&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authResults&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dkim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dmarc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authResults&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Any failure on a supposedly internal or financial email = flag&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;spf&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dkim&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dmarc&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pass&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Signal That Doesn't Depend on Content
&lt;/h2&gt;

&lt;p&gt;Authentication checks require access to headers. The signal that works at the human layer — and that AI cannot defeat — is the &lt;strong&gt;request pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Legitimate organizations have consistent behavioral signatures:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PHISHING_REQUEST_PATTERNS&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;wire transfer outside normal approval chain&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;request for credentials or MFA codes via email&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;urgency to bypass standard process&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;confidentiality instruction (do not tell X)&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;new payment method or vendor not in system&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;action requested on behalf of unavailable approver&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;// The key insight: legitimate urgent requests&lt;/span&gt;
&lt;span class="c1"&gt;// arrive through established channels with context.&lt;/span&gt;
&lt;span class="c1"&gt;// Phishing creates the urgency in the email itself.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern holds regardless of how the email is written. AI can generate perfect prose but cannot change the fact that a real CFO initiating a real wire transfer uses the company's actual payment system, not a direct email to a finance manager with a new bank account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a Detection Heuristic
&lt;/h2&gt;

&lt;p&gt;For teams building email security tooling or internal automation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phishing_risk_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="c1"&gt;# Authentication failures (high weight)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spf&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dkim&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dmarc&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

    &lt;span class="c1"&gt;# Domain analysis
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_lookalike_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;

    &lt;span class="c1"&gt;# Request pattern signals (content analysis)
&lt;/span&gt;    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wire transfer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bank account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;routing number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;urgent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;immediately&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;today only&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;close of business&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keep this confidential&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t mention&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;just between us&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

    &lt;span class="c1"&gt;# High score = route to additional verification, not auto-block
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_lookalike_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legitimate_domains&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jellyfish&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jaro_winkler_similarity&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;jaro_winkler_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt; 
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;legit&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;legit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;legitimate_domains&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design decision: high-risk emails should trigger an out-of-band verification requirement, not an auto-block. Auto-blocking has false positive costs; requiring phone verification for flagged financial requests has almost none.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Defense That Defeats All Variants
&lt;/h2&gt;

&lt;p&gt;Out-of-band verification: any email requesting financial action, credential changes, or process exceptions gets verified via phone call to a number already on record.&lt;/p&gt;

&lt;p&gt;This rule is architecturally sound because it breaks the attack at the social engineering layer regardless of how convincing the email is. It doesn't matter how good the AI gets at writing emails — it can't intercept a phone call the target initiates to a known number.&lt;/p&gt;

&lt;p&gt;The consumer version of this — what non-technical users should watch for — is at &lt;a href="https://lucas8.com/how-to-spot-ai-phishing-email" rel="noopener noreferrer"&gt;lucas8.com/how-to-spot-ai-phishing-email&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
    </item>
    <item>
      <title>What a VPN Actually Does (And Why Most Devs Use It Wrong)</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 11 Jun 2026 17:02:35 +0000</pubDate>
      <link>https://dev.to/spicykim/what-a-vpn-actually-does-and-why-most-devs-use-it-wrong-8ok</link>
      <guid>https://dev.to/spicykim/what-a-vpn-actually-does-and-why-most-devs-use-it-wrong-8ok</guid>
      <description>&lt;p&gt;Every developer I know has a VPN. Most of them have it running while they're logged into Google, sending data through Chrome, and using apps that do their own certificate pinning — which means the VPN is protecting approximately nothing meaningful in that moment.&lt;/p&gt;

&lt;p&gt;This isn't a knock on VPNs. It's a scoping problem. Here's what the tool actually covers, what leaks around it, and how to test it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Actually Happening at the Network Layer
&lt;/h2&gt;

&lt;p&gt;A VPN operates at Layer 3 (Network) of the OSI model. It creates an encrypted tunnel — typically using WireGuard, OpenVPN, or IKEv2/IPSec — between your device and a VPN server. All IP traffic gets routed through that tunnel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protocol comparison:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Security&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WireGuard&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Fast&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Strong&lt;/td&gt;
&lt;td&gt;UDP 51820&lt;/td&gt;
&lt;td&gt;Modern, audited, ~4000 lines of code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenVPN&lt;/td&gt;
&lt;td&gt;⭐⭐ Medium&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Strong&lt;/td&gt;
&lt;td&gt;TCP 443 / UDP 1194&lt;/td&gt;
&lt;td&gt;Battle-tested, ~100k lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IKEv2/IPSec&lt;/td&gt;
&lt;td&gt;⭐⭐⭐ Fast&lt;/td&gt;
&lt;td&gt;⭐⭐ Good&lt;/td&gt;
&lt;td&gt;UDP 500/4500&lt;/td&gt;
&lt;td&gt;Native on mobile, good reconnect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2TP/IPSec&lt;/td&gt;
&lt;td&gt;⭐ Slow&lt;/td&gt;
&lt;td&gt;⭐ Weak&lt;/td&gt;
&lt;td&gt;UDP 1701&lt;/td&gt;
&lt;td&gt;Avoid — potentially compromised&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;WireGuard is the correct choice in 2026 unless you have a specific reason not to use it. Smaller codebase = smaller attack surface. Most audited VPN providers now use it by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Leaks Around the Tunnel
&lt;/h2&gt;

&lt;p&gt;The VPN handles IP routing. It doesn't handle everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS leaks&lt;/strong&gt; are the most common issue. If your system's DNS resolver isn't explicitly routed through the tunnel, your DNS queries go directly to your ISP — revealing every domain you visit even with the VPN active.&lt;/p&gt;

&lt;p&gt;Test this from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check your current DNS resolver&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/resolv.conf

&lt;span class="c"&gt;# Test for DNS leak (run while VPN is active)&lt;/span&gt;
&lt;span class="c"&gt;# Should show your VPN provider's DNS, not your ISP's&lt;/span&gt;
nslookup whoami.akamai.net

&lt;span class="c"&gt;# More thorough test&lt;/span&gt;
dig +short myip.opendns.com @resolver1.opendns.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most reputable VPN clients handle DNS routing automatically, but worth verifying — especially on Linux where DNS management is fragmented across systemd-resolved, dnsmasq, and NetworkManager depending on your distro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebRTC leaks&lt;/strong&gt; expose your real IP through browser APIs even when a VPN is active. This is a browser-layer problem, not a network-layer problem.&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;// WebRTC leak test — run in browser console while on VPN&lt;/span&gt;
&lt;span class="c1"&gt;// If this returns your real IP, you have a WebRTC leak&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stun:stun.l.google.com:19302&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="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onicecandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&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;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&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;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IP exposed via WebRTC:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;Fix: Firefox has &lt;code&gt;media.peerconnection.enabled&lt;/code&gt; in &lt;code&gt;about:config&lt;/code&gt;. Chrome requires an extension or a VPN client with WebRTC leak protection built in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application-layer tracking&lt;/strong&gt; doesn't touch the network layer at all. If you're authenticated in your browser, that session follows you. Cookies, localStorage, IndexedDB — none of this is affected by routing your IP through Amsterdam.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Kill Switch and Why It Matters
&lt;/h2&gt;

&lt;p&gt;A kill switch blocks all traffic if the VPN connection drops. Without it, your traffic briefly reverts to your real IP when the tunnel reconnects. This is the correct default for any privacy-sensitive setup.&lt;/p&gt;

&lt;p&gt;On Linux with ufw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Block all traffic by default&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny outgoing
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming

&lt;span class="c"&gt;# Allow only traffic through VPN interface (tun0 for OpenVPN, wg0 for WireGuard)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow out on tun0
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow out on wg0

&lt;span class="c"&gt;# Allow LAN traffic if needed&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow out on eth0 to 192.168.0.0/16
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow out on eth0 to 10.0.0.0/8

&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WireGuard users can use &lt;code&gt;PostUp&lt;/code&gt;/&lt;code&gt;PreDown&lt;/code&gt; hooks in the config for a more integrated approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Interface]&lt;/span&gt;
&lt;span class="py"&gt;PrivateKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;your_private_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.x.x.x/32&lt;/span&gt;
&lt;span class="py"&gt;DNS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1.1.1.1&lt;/span&gt;

&lt;span class="py"&gt;PostUp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -j REJECT&lt;/span&gt;
&lt;span class="py"&gt;PreDown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;iptables -D OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -j REJECT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Split Tunneling: Route Only What You Need
&lt;/h2&gt;

&lt;p&gt;Split tunneling lets you route specific traffic through the VPN while everything else goes directly. Useful when you need VPN for specific services but don't want to tunnel your local development traffic or internal network requests.&lt;/p&gt;

&lt;p&gt;Most GUI clients support this natively. For WireGuard directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Peer]&lt;/span&gt;
&lt;span class="py"&gt;PublicKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;server_public_key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;server_ip&amp;gt;:51820&lt;/span&gt;

&lt;span class="c"&gt;# Route only specific subnets through VPN instead of all traffic
&lt;/span&gt;&lt;span class="py"&gt;AllowedIPs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.0.0.0/8, 172.16.0.0/12&lt;/span&gt;

&lt;span class="c"&gt;# vs. route everything:
# AllowedIPs = 0.0.0.0/0, ::/0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What a VPN Actually Buys You (Honest Summary)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Your ISP can't see which domains you visit&lt;/li&gt;
&lt;li&gt;✅ Traffic encrypted against public WiFi eavesdropping&lt;/li&gt;
&lt;li&gt;✅ Your IP hidden from destination servers&lt;/li&gt;
&lt;li&gt;✅ DNS queries protected (if configured correctly)&lt;/li&gt;
&lt;li&gt;❌ No protection against cookie/session tracking&lt;/li&gt;
&lt;li&gt;❌ No protection against browser fingerprinting&lt;/li&gt;
&lt;li&gt;❌ No protection while authenticated in any app or service&lt;/li&gt;
&lt;li&gt;❌ No protection from the VPN provider themselves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool solves a specific set of network-layer problems. For application-layer privacy, you need application-layer solutions — content blocking, session isolation, fingerprint-resistant browsers.&lt;/p&gt;

&lt;p&gt;If you're setting up a VPN for a team or personal setup and want the technical layer done properly: WireGuard, verified DNS routing, kill switch enabled, and DNS leak tested before you trust it. Everything else is marketing.&lt;/p&gt;




&lt;p&gt;Consumer version — what this means for people who don't want to touch iptables: &lt;a href="https://lucas8.com/what-vpn-actually-protects" rel="noopener noreferrer"&gt;lucas8.com/what-vpn-actually-protects&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Passkeys Under the Hood: What's Actually Happening When You Use Face ID to Log In</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 11 Jun 2026 11:57:02 +0000</pubDate>
      <link>https://dev.to/spicykim/passkeys-under-the-hood-whats-actually-happening-when-you-use-face-id-to-log-in-2md1</link>
      <guid>https://dev.to/spicykim/passkeys-under-the-hood-whats-actually-happening-when-you-use-face-id-to-log-in-2md1</guid>
      <description>&lt;p&gt;Every developer I know understands that passwords are broken. What fewer people have actually dug into is &lt;em&gt;why&lt;/em&gt; passkeys fix this at the protocol level — and how surprisingly simple the WebAuthn API is to implement.&lt;/p&gt;

&lt;p&gt;Here's the full picture: what's happening cryptographically, how the browser API works, and a quick comparison of the libraries worth using in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem With Passwords (The Actual Technical One)
&lt;/h2&gt;

&lt;p&gt;Passwords fail not because users pick weak ones. They fail because of how the authentication model works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User creates a password&lt;/li&gt;
&lt;li&gt;Server stores a hash of it&lt;/li&gt;
&lt;li&gt;User sends the password on every login&lt;/li&gt;
&lt;li&gt;Server hashes it and compares&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 is the problem. The credential gets transmitted. That transmission can be intercepted (phishing), the hash can be cracked (breach), or the same credential works on other sites (credential stuffing). Every "password best practice" is mitigation, not a fix.&lt;/p&gt;

&lt;p&gt;Passkeys change the model entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Passkeys Actually Work
&lt;/h2&gt;

&lt;p&gt;Passkeys use asymmetric cryptography (FIDO2/WebAuthn). Here's the flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registration:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server sends a random challenge&lt;/li&gt;
&lt;li&gt;Device generates a public/private key pair&lt;/li&gt;
&lt;li&gt;Private key stays on device (in Secure Enclave / TPM)&lt;/li&gt;
&lt;li&gt;Public key + signed challenge sent to server&lt;/li&gt;
&lt;li&gt;Server stores public key only&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server sends a new random challenge&lt;/li&gt;
&lt;li&gt;User approves with biometric/PIN&lt;/li&gt;
&lt;li&gt;Device signs the challenge with private key&lt;/li&gt;
&lt;li&gt;Server verifies signature using stored public key&lt;/li&gt;
&lt;li&gt;Done — private key never leaves the device&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The private key is never transmitted. Ever. The server never stores anything that can be used to impersonate the user. A phishing page that captures the signed challenge gets nothing reusable — challenges are single-use nonces.&lt;/p&gt;




&lt;h2&gt;
  
  
  The WebAuthn API in Practice
&lt;/h2&gt;

&lt;p&gt;The browser API maps directly to that flow. Registration looks like this:&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;// Registration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;serverChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Uint8Array from your server&lt;/span&gt;
    &lt;span class="na"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yourapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Uint8Array, not a username&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;pubKeyCredParams&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="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// ES256 (preferred)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;257&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// RS256 (fallback)&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;residentKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Enables passkey sync&lt;/span&gt;
      &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;    &lt;span class="c1"&gt;// Requires biometric/PIN&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&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;// Send credential.response to your server for verification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication is simpler:&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;// Authentication&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;serverChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rpId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yourapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send assertion.response to your server for verification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server-side verification is where most devs reach for a library — parsing and verifying the CBOR-encoded attestation objects by hand is painful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Library Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Passkey Support&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://simplewebauthn.dev/" rel="noopener noreferrer"&gt;SimpleWebAuthn&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;Best DX, actively maintained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/duo-labs/py_webauthn" rel="noopener noreferrer"&gt;py_webauthn&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;Duo Labs, solid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/webauthn4j/webauthn4j" rel="noopener noreferrer"&gt;webauthn4j&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;Spring integration available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/go-webauthn/webauthn" rel="noopener noreferrer"&gt;go-webauthn&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;Clean API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://hanko.io/" rel="noopener noreferrer"&gt;Hanko&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Hosted&lt;/td&gt;
&lt;td&gt;✅ Managed&lt;/td&gt;
&lt;td&gt;Drop-in if you don't want to own the flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://passage.1password.com/" rel="noopener noreferrer"&gt;Passage by 1Password&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Hosted&lt;/td&gt;
&lt;td&gt;✅ Managed&lt;/td&gt;
&lt;td&gt;Generous free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most teams: &lt;strong&gt;SimpleWebAuthn&lt;/strong&gt; for Node, &lt;strong&gt;py_webauthn&lt;/strong&gt; for Python. If you want to skip the implementation entirely and just own the UX, Hanko's self-hostable version is genuinely good.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Things That Catch People Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;residentKey: "required"&lt;/code&gt; is what makes it a passkey&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without this, you're implementing a regular FIDO2 security key flow — the credential isn't stored on the device and won't sync to iCloud/Google. If you want the "tap Face ID to log in" experience, &lt;code&gt;residentKey: "required"&lt;/code&gt; and &lt;code&gt;userVerification: "required"&lt;/code&gt; are both mandatory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cross-device auth uses CTAP2 + BLE proximity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user authenticates on a desktop with their phone, the desktop and phone establish a BLE proximity check to prevent remote attacks, then the phone signs the challenge and sends the signature back via a relay server. The private key never leaves the phone. Worth understanding before you field questions about "is it safe to use my phone to log into someone else's computer."&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Actually Implement First
&lt;/h2&gt;

&lt;p&gt;If you're adding passkeys to an existing app: don't rip out passwords. Add passkeys as an &lt;em&gt;additional&lt;/em&gt; auth method first. Let users opt in. The fallback matters — not every user has a device that supports passkeys smoothly yet, and some enterprise environments block biometric auth entirely.&lt;/p&gt;

&lt;p&gt;The happy path is excellent. The failure modes are what you need to design for. Test especially: browser without platform authenticator support, iOS 15 (passkey sync requires iOS 16+), and password manager conflicts when users have both a passkey and a saved password for the same domain.&lt;/p&gt;




&lt;p&gt;The consumer version of this — what passkeys mean for regular users who don't care about WebAuthn — is over at &lt;a href="https://lucas8.com/lucas8.com/passkeys-vs-passwords-security" rel="noopener noreferrer"&gt;lucas8.com/passkeys-vs-passwords-security&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your Smart Home Is Collecting Data You Never Agreed To — Here's How to Audit It</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 04 Jun 2026 15:49:40 +0000</pubDate>
      <link>https://dev.to/spicykim/your-smart-home-is-collecting-data-you-never-agreed-to-heres-how-to-audit-it-11gn</link>
      <guid>https://dev.to/spicykim/your-smart-home-is-collecting-data-you-never-agreed-to-heres-how-to-audit-it-11gn</guid>
      <description>&lt;p&gt;Most developers I know have locked-down laptops — password managers, 2FA everywhere, encrypted drives. Then they go home and have a robot vacuum with a cloud-synced floor plan of their apartment, a smart TV that screenshots their screen every few minutes, and a doorbell camera on default settings.&lt;/p&gt;

&lt;p&gt;The home network is the gap. Here's a device-by-device audit you can run in 30 minutes, with the specific settings to change on each platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Smart Home Defaults Are Set for Data Collection, Not Privacy
&lt;/h2&gt;

&lt;p&gt;Every smart home device ships with a companion app and a terms of service most people skip. The defaults in those apps are almost universally set to maximize data collection — because that data has value to manufacturers, advertisers, and in some cases, third-party brokers.&lt;/p&gt;

&lt;p&gt;A few concrete examples before we get into the audit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart TVs&lt;/strong&gt; use ACR (Automatic Content Recognition) to take screenshots of whatever is on screen — including HDMI input from game consoles or set-top boxes — and send them to the manufacturer for ad targeting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iRobot Roomba&lt;/strong&gt; (now owned by Amazon) floor maps can be accessed within the Amazon ecosystem. In 2022, images captured during cleaning were found to have been used in AI training datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Ring&lt;/strong&gt; previously allowed law enforcement to request footage directly from Amazon servers, bypassing homeowners. This practice ended after a &lt;a href="https://www.ftc.gov/news-events/news/press-releases/2023/05/ftc-charges-amazon-ring-illegally-surveilled-customers" rel="noopener noreferrer"&gt;2023 FTC settlement&lt;/a&gt; that resulted in a $5.8 million fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hidden — it's in the privacy policies. But defaults being what they are, most users never change them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Device-by-Device Audit Checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Smart TV — Disable ACR
&lt;/h3&gt;

&lt;p&gt;ACR is the big one. Here's where to find it by brand:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Brand&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Samsung&lt;/td&gt;
&lt;td&gt;Settings → Support → Terms &amp;amp; Privacy → Viewing Information Services → Off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LG&lt;/td&gt;
&lt;td&gt;Settings → All Settings → General → About This TV → User Agreements → disable "Personalized Advertising"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vizio&lt;/td&gt;
&lt;td&gt;Settings → System → Reset &amp;amp; Admin → Viewing Data → Off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sony (Google TV)&lt;/td&gt;
&lt;td&gt;Settings → Privacy → Ads → Opt out of Ads Personalization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While you're in there: disable the microphone if you don't use voice commands. It's usually under Settings → General → Voice or Smart Features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Robot Vacuum — Delete Maps, Review Sharing
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the companion app (iRobot Home, Roborock, Ecovacs Home, etc.)&lt;/li&gt;
&lt;li&gt;Navigate to Privacy or Account Settings&lt;/li&gt;
&lt;li&gt;Delete saved maps&lt;/li&gt;
&lt;li&gt;Opt out of data sharing / analytics&lt;/li&gt;
&lt;li&gt;Check if your model supports local-only map processing — some Roborock models do&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're using a Roomba and have it linked to Alexa, be aware that map data flows into the Amazon ecosystem. You can limit this by removing the Alexa skill and keeping accounts unlinked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doorbell / Security Cameras — Review Cloud Storage and Law Enforcement Policy
&lt;/h3&gt;

&lt;p&gt;The two things to check:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud storage scope:&lt;/strong&gt; Most cameras upload continuously. Review whether you're on a plan that stores footage on the company's servers indefinitely, and whether you can switch to local storage (NAS, SD card) for sensitive areas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Law enforcement policy:&lt;/strong&gt; Ring, Nest, Arlo, and most major brands publish transparency reports. Look up your brand's policy on government data requests. Since the Ring FTC settlement, consent is nominally required — but warrant-based access still applies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Facial recognition:&lt;/strong&gt; If your camera offered this as a feature (Google Nest Aware had it; Ring has offered versions of it), check if it's enabled and consider disabling it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Home Network — Isolate Your IoT Devices
&lt;/h3&gt;

&lt;p&gt;This is the most impactful single change you can make from a security standpoint.&lt;/p&gt;

&lt;p&gt;Most consumer routers support a guest network. Put all your smart home devices on it. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A compromised IoT device can't pivot to your laptops or NAS&lt;/li&gt;
&lt;li&gt;Cross-device data correlation between your phone and your vacuum is broken at the network layer&lt;/li&gt;
&lt;li&gt;You can monitor IoT traffic separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want more visibility, &lt;a href="https://www.fing.com/" rel="noopener noreferrer"&gt;Fing&lt;/a&gt; (free tier) gives you a device inventory and flags unusual traffic patterns without requiring router-level config changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phone App Permissions — Revoke What Isn't Needed
&lt;/h3&gt;

&lt;p&gt;Smart home apps accumulate permissions over time. Audit them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&gt; Settings → Privacy &amp;amp; Security → review Microphone, Camera, Location, Contacts&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt; Settings → Privacy → Permission Manager → review by permission type&lt;/p&gt;

&lt;p&gt;General rule: a smart bulb app has no legitimate reason to access your microphone. A robot vacuum app doesn't need your contacts. Location access should be "While Using" at most, not "Always."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Longer-Term Fix: Matter Protocol
&lt;/h2&gt;

&lt;p&gt;The industry is slowly moving toward &lt;a href="https://csa-iot.org/all-solutions/matter/" rel="noopener noreferrer"&gt;Matter&lt;/a&gt; — an open smart home standard backed by Apple, Google, Amazon, and Samsung. The key privacy advantage: Matter-compatible devices can operate locally on your home network without cloud dependency for basic functions.&lt;/p&gt;

&lt;p&gt;Local processing means less data leaving your network by default. It's not a complete privacy solution, but it's a meaningful architectural improvement over the current cloud-first model.&lt;/p&gt;

&lt;p&gt;When you're next replacing a device, filtering for Matter compatibility is worth adding to your checklist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Key Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Smart TV&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Disable ACR, turn off microphone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robot Vacuum&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Delete maps, review data sharing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cameras&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;Review cloud storage, disable facial recognition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home Network&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Move IoT to guest network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone Apps&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Revoke microphone, location, contacts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;The defaults on every one of these devices were chosen by the manufacturer. Spending 30 minutes changing them is the most direct form of control you have over what your home network is actually doing.&lt;/p&gt;

&lt;p&gt;Full version with more detail on each step: &lt;a href="https://lucas8.com/smart-home-privacy-audit-guide" rel="noopener noreferrer"&gt;lucas8.com/smart-home-privacy-audit-guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The 7 Moves Identity Thieves Pray You Never Make</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Thu, 04 Jun 2026 15:46:42 +0000</pubDate>
      <link>https://dev.to/spicykim/the-7-moves-identity-thieves-pray-you-never-make-eci</link>
      <guid>https://dev.to/spicykim/the-7-moves-identity-thieves-pray-you-never-make-eci</guid>
      <description>&lt;p&gt;Every 4.9 seconds, someone in the US becomes an identity theft victim. Over 1.1 million FTC reports in 2024. $12.5 billion in losses. And the uncomfortable truth? Most of it wasn't sophisticated hacking. It was reused passwords, unfrozen credit, and forgotten accounts.&lt;/p&gt;

&lt;p&gt;Here's what actually works in 2026 — and why most people still aren't doing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 1: Freeze Your Credit at All Three Bureaus
&lt;/h2&gt;

&lt;p&gt;This is the highest-ROI security action most people never take. A credit freeze blocks any new lender from accessing your file — no access, no fraudulent new accounts.&lt;/p&gt;

&lt;p&gt;It's free by federal law. Takes ~10 minutes per bureau. Zero impact on your credit score.&lt;/p&gt;

&lt;p&gt;The critical detail most guides skip: &lt;strong&gt;you must freeze all three separately&lt;/strong&gt;. A freeze at Equifax does nothing at Experian or TransUnion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bureau&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;Phone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Equifax&lt;/td&gt;
&lt;td&gt;equifax.com/credit-freeze&lt;/td&gt;
&lt;td&gt;1-888-298-0045&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experian&lt;/td&gt;
&lt;td&gt;experian.com/help/credit-freeze&lt;/td&gt;
&lt;td&gt;1-888-397-3742&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TransUnion&lt;/td&gt;
&lt;td&gt;transunion.com/credit-freeze&lt;/td&gt;
&lt;td&gt;1-800-916-8800&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you need to apply for credit, lift temporarily at the relevant bureau, then re-freeze. If you have kids: freeze their SSNs too — children's numbers are prime targets because they have zero monitoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 2: Run a Breach Check Right Now
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://haveibeenpwned.com" rel="noopener noreferrer"&gt;haveibeenpwned.com&lt;/a&gt; and enter every email address you use, including old ones. It searches hundreds of confirmed breach dumps and tells you exactly which services exposed your account.&lt;/p&gt;

&lt;p&gt;If any match: assume every password you used on that site is compromised. Hashed passwords aren't safe — most common hash formats have been cracked at scale by now.&lt;/p&gt;

&lt;p&gt;For ongoing coverage, Google Password Manager's built-in Password Checkup automatically flags saved credentials that appear in new breach data. Free, no setup required beyond using Chrome or Google's password manager.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 3: Stop Reusing Passwords
&lt;/h2&gt;

&lt;p&gt;Credential stuffing is fully industrialized in 2026. Attackers buy breach data in bulk, run automated tools against hundreds of sites simultaneously, and your reused &lt;code&gt;YourName2019!&lt;/code&gt; password is already in a combolist somewhere.&lt;/p&gt;

&lt;p&gt;In 2025 alone, over 1.8 billion login credentials were stolen from infected devices (Recorded Future). Infostealer malware families like Lumma and RedLine scrape browser password vaults silently — your saved passwords in Chrome are a high-value target.&lt;/p&gt;

&lt;p&gt;The fix is mechanical: use a password manager with unique, randomly generated passwords for every site.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free + open source&lt;/strong&gt;: Bitwarden&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid, well-regarded&lt;/strong&gt;: 1Password, Dashlane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You remember one master password. The manager handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 4: Enable 2FA — Especially on Email
&lt;/h2&gt;

&lt;p&gt;A strong unique password stops most attacks. 2FA stops almost everything else.&lt;/p&gt;

&lt;p&gt;Enable it everywhere that matters, in this priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Email first&lt;/strong&gt; — whoever controls your inbox can reset access to everything else&lt;/li&gt;
&lt;li&gt;Banking and financial accounts&lt;/li&gt;
&lt;li&gt;SSA.gov (create a My Social Security account, enable 2FA, block fraudulent benefit claims)&lt;/li&gt;
&lt;li&gt;Apple ID / Google Account&lt;/li&gt;
&lt;li&gt;Anything storing payment data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Authenticator apps &amp;gt; SMS.&lt;/strong&gt; SMS 2FA can be bypassed via SIM-swap attacks — a thief convinces your carrier to transfer your number to their device. App-based codes (Google Authenticator, Authy, Microsoft Authenticator) don't have this vulnerability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 5: Shrink Your Attack Surface
&lt;/h2&gt;

&lt;p&gt;Two categories to trim:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Old accounts&lt;/strong&gt;: Every forgotten account is a potential breach vector. Use &lt;a href="https://justdeleteme.xyz" rel="noopener noreferrer"&gt;justdeleteme.xyz&lt;/a&gt; to find deletion instructions for hundreds of services. If you can't delete, at least change the password to something unique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data brokers&lt;/strong&gt;: These companies aggregate your address, phone number, relatives, and enough detail to answer your security questions — then sell it to anyone who pays. Automated opt-out tools (DeleteMe, Kanary) handle the removal process since there are hundreds of brokers and each has its own opt-out flow.&lt;/p&gt;

&lt;p&gt;Smaller digital footprint = harder to build a targeting profile against you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 6: Know the Attack Vectors
&lt;/h2&gt;

&lt;p&gt;Understanding how the attack actually happens makes prevention concrete rather than abstract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phishing&lt;/strong&gt; remains the dominant entry point — a convincing email, text, or call impersonating your bank, the IRS, or a delivery service. AI-generated voice cloning has made phone-based phishing significantly more convincing in 2026: callers now sound exactly like your bank's fraud team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data breaches&lt;/strong&gt; are largely outside your control at the company level. The ITRC recorded 3,322 data compromises in 2025 — a 79% five-year jump — generating 140 million victim notices in Q1 2026 alone. Your mitigation: unique passwords ensure one breach doesn't cascade into twenty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Social engineering via public profiles&lt;/strong&gt;: Your dog's name in your Instagram bio, your employer on LinkedIn, your birthday on Facebook — that's often enough to answer security questions and trigger a password reset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mail theft&lt;/strong&gt;: Old-fashioned, but still real. Switch everything to paperless statements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Move 7: Know the Early Warning Signs
&lt;/h2&gt;

&lt;p&gt;Thieves often sit on stolen data for months before using it — waiting for breach-related fraud alerts to expire. These are the signals to watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unexpected hard inquiries on your credit report&lt;/li&gt;
&lt;li&gt;Collection notices for accounts you never opened&lt;/li&gt;
&lt;li&gt;Tax return rejection (someone filed using your SSN already)&lt;/li&gt;
&lt;li&gt;Health insurer denial for treatment you never received&lt;/li&gt;
&lt;li&gt;Password reset emails or login notifications you didn't request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Free monitoring tools&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.annualcreditreport.com" rel="noopener noreferrer"&gt;AnnualCreditReport.com&lt;/a&gt; — one free report per bureau per year (stagger them for year-round coverage)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.identitytheft.gov" rel="noopener noreferrer"&gt;IdentityTheft.gov&lt;/a&gt; — FTC's recovery plan generator if you're already a victim&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Honest Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Move&lt;/th&gt;
&lt;th&gt;Time to Set Up&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Credit freeze (all 3)&lt;/td&gt;
&lt;td&gt;~30 min&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Blocks new fraudulent accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breach check (HIBP)&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Surfaces compromised credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password manager&lt;/td&gt;
&lt;td&gt;1–2 hours&lt;/td&gt;
&lt;td&gt;Free–$3/mo&lt;/td&gt;
&lt;td&gt;Stops credential stuffing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2FA on email + banking&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Stops most account takeovers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete old accounts&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Reduces breach surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paperless statements&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Eliminates mail theft vector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credit monitoring&lt;/td&gt;
&lt;td&gt;Ongoing&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Early detection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these require technical expertise. The hardest part is starting. The credit freeze takes about 30 minutes across all three bureaus — do that first, today, before anything else.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Original post: &lt;a href="https://lucas8.com/identity-theft-prevention-moves" rel="noopener noreferrer"&gt;lucas8.com/identity-theft-prevention-moves&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fitness Tracker Privacy in 2026: Fitbit vs Garmin vs Apple Watch vs Oura (What the Data Actually Shows)</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Wed, 03 Jun 2026 16:13:03 +0000</pubDate>
      <link>https://dev.to/spicykim/fitness-tracker-privacy-in-2026-fitbit-vs-garmin-vs-apple-watch-vs-oura-what-the-data-actually-57me</link>
      <guid>https://dev.to/spicykim/fitness-tracker-privacy-in-2026-fitbit-vs-garmin-vs-apple-watch-vs-oura-what-the-data-actually-57me</guid>
      <description>&lt;p&gt;You wear your fitness tracker 24/7. It knows your resting heart rate, your sleep cycles, your GPS routes, your stress levels, and — depending on the model — your blood oxygen, menstrual cycle, and ECG readings.&lt;/p&gt;

&lt;p&gt;Here's the question most people never think to ask: who else has access to that data?&lt;/p&gt;

&lt;p&gt;The answer depends almost entirely on which brand you're wearing. And the gap between the best and worst performers is significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  The HIPAA Gap (Most People Get This Wrong)
&lt;/h2&gt;

&lt;p&gt;Before anything else: &lt;strong&gt;your fitness tracker data is almost certainly not covered by HIPAA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HIPAA applies to &lt;em&gt;covered entities&lt;/em&gt; — hospitals, health insurers, healthcare clearinghouses, and their direct business associates. Consumer wearable companies (Fitbit, Garmin, Apple, Whoop, Oura) are none of these.&lt;/p&gt;

&lt;p&gt;Your smartwatch heart rate data has fewer legal protections than a doctor's handwritten note. The only frameworks that partially apply are state laws — California's CCPA, Illinois' BIPA — and even those have significant gaps.&lt;/p&gt;

&lt;p&gt;What you're left with: each company's own privacy policy. Let's go through them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fitbit (Now Google Health): ⚠️ Caution
&lt;/h2&gt;

&lt;p&gt;As of May 2026, all Fitbit accounts have migrated to Google accounts. Your health data is now governed by Google's privacy policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the data says:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fitbit collects &lt;strong&gt;23 data types&lt;/strong&gt; per Apple's App Store privacy labels — the most of any tracker in this comparison&lt;/li&gt;
&lt;li&gt;Google has committed that Fitbit health data won't be used for Google Ads&lt;/li&gt;
&lt;li&gt;The privacy policy still permits sharing aggregated/de-identified data for "research and commercial purposes"&lt;/li&gt;
&lt;li&gt;Analytics SDKs from Meta and Google are embedded in the app, transmitting usage data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The re-identification problem:&lt;/strong&gt; A 2024 Imperial College London study found that supposedly anonymous fitness datasets could be re-identified with &lt;strong&gt;87% accuracy&lt;/strong&gt; using just three data points: age range, zip code, and activity pattern. "De-identified" isn't as safe as it sounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In 2026:&lt;/strong&gt; Whoop faces a class-action lawsuit in California for data-sharing practices with advertising partners — a signal of where regulatory pressure is heading across the industry.&lt;/p&gt;

</description>
      <category>security</category>
      <category>beginners</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Smart Home Devices Are Collecting More Than You Think — Here's What to Do</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Sun, 31 May 2026 08:42:21 +0000</pubDate>
      <link>https://dev.to/spicykim/smart-home-devices-are-collecting-more-than-you-think-heres-what-to-do-3hn6</link>
      <guid>https://dev.to/spicykim/smart-home-devices-are-collecting-more-than-you-think-heres-what-to-do-3hn6</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Reads the Privacy Policy For
&lt;/h2&gt;

&lt;p&gt;93% of American households now own at least one smart home device. According to the 2026 Copeland Smart Home Data Privacy Study, 57% of those owners are worried about how their data is being used — and 55% have little to no idea what their smart thermostat actually sends back to the manufacturer.&lt;/p&gt;

&lt;p&gt;That gap between adoption and understanding is where the real risk lives.&lt;/p&gt;

&lt;p&gt;This post covers what the major device categories actually collect, what happens to that data downstream, and the specific settings worth changing today. No tinfoil hats. Just the defaults that are set against your interests.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Collected, by Device Type
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Smart Speakers (Echo, Google Nest, HomePod)
&lt;/h3&gt;

&lt;p&gt;All three platforms use continuous wake-word detection, which means audio is always being processed locally. The problem is accidental activations: researchers at Northwestern University and Imperial College London documented Google Home Mini triggering ~0.95 times per hour during passive TV playback. Each trigger sends audio to the cloud.&lt;/p&gt;

&lt;p&gt;Both Amazon and Google have acknowledged using human contractors to review voice samples. This isn't theoretical — it's documented and settled. The recordings persist unless you actively delete them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's downstream:&lt;/strong&gt; Voice data is used to train speech models. This matters more than it used to — a voice clip as short as three seconds is sufficient for modern voice synthesis tools to generate a convincing clone. See the implications in this related piece on &lt;a href="https://lucas8.com/smart-home-device-spying-privacy-risks" rel="noopener noreferrer"&gt;AI voice cloning fraud&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart TVs
&lt;/h3&gt;

&lt;p&gt;Virtually every major smart TV manufacturer ships with Automatic Content Recognition (ACR) enabled by default. ACR takes periodic screenshots of what's on screen — regardless of input source — and reports it back to the manufacturer.&lt;/p&gt;

&lt;p&gt;The data profile includes: what you watch, when you watch, how long, and on what input. This is sold to advertising networks and, in some documented cases, to insurance companies running behavioral risk models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;ACR Setting Name&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Samsung&lt;/td&gt;
&lt;td&gt;Viewing Information Services&lt;/td&gt;
&lt;td&gt;Settings → Support → Terms &amp;amp; Policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LG&lt;/td&gt;
&lt;td&gt;LivePlus&lt;/td&gt;
&lt;td&gt;Settings → All Settings → General&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vizio&lt;/td&gt;
&lt;td&gt;Smart Interactivity&lt;/td&gt;
&lt;td&gt;Menu → System&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roku (all brands)&lt;/td&gt;
&lt;td&gt;Limit Ad Tracking&lt;/td&gt;
&lt;td&gt;Settings → Privacy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Disabling ACR has zero effect on streaming functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart Thermostats
&lt;/h3&gt;

&lt;p&gt;Thermostat data is behavioral at the most granular level: wake time, departure time, return time, sleep time — every day. The 2026 Copeland study found concern about data privacy among thermostat owners grew from 26% in 2022 to 37% in 2026. The Nest thermostat also uses your phone's GPS by default to determine home/away status, which means Google maintains a continuous location record tied to your home presence patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Network Risk Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Individual device privacy settings matter. But the larger threat is architectural.&lt;/p&gt;

&lt;p&gt;Bitdefender's 2025 threat intelligence data found that connected homes averaged &lt;strong&gt;29 daily attack attempts&lt;/strong&gt; — a 3× increase year-over-year. The attack vector is almost always the same: a device with default credentials, unpatched firmware, or a known CVE that the manufacturer never fixed.&lt;/p&gt;

&lt;p&gt;A compromised smart bulb isn't dangerous because someone controls your lights. It's a lateral movement opportunity into the same network segment where your laptop, phone, and financial sessions live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix: network segmentation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most consumer routers support a guest network. The correct configuration is simple:&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Wasting 32% of Your Cloud Budget: A FinOps Playbook for DevOps Engineers</title>
      <dc:creator>Spicy</dc:creator>
      <pubDate>Sat, 30 May 2026 15:37:09 +0000</pubDate>
      <link>https://dev.to/spicykim/stop-wasting-32-of-your-cloud-budget-a-finops-playbook-for-devops-engineers-3led</link>
      <guid>https://dev.to/spicykim/stop-wasting-32-of-your-cloud-budget-a-finops-playbook-for-devops-engineers-3led</guid>
      <description>&lt;p&gt;You're in a sprint planning meeting and someone drops the monthly cloud bill on the table. It's up again. Nobody knows exactly why. The DevOps lead says it's probably the new microservices rollout. Finance says it's been climbing for six months. Everyone nods and moves on.&lt;/p&gt;

&lt;p&gt;This is a story playing out in thousands of engineering orgs right now. According to the FinOps Foundation's State of FinOps 2026 survey — which aggregated data from 1,200+ organizations running over $69 billion in cloud spend — teams without a structured optimization practice waste 32 to 40 percent of their cloud budget every month.&lt;/p&gt;

&lt;p&gt;For a $500K/year cloud budget, that's $160K to $200K disappearing into idle instances, orphaned volumes, and on-demand pricing on workloads that have been running predictably for years.&lt;/p&gt;

&lt;p&gt;Here's the 5-tactic playbook to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tactic 1 — Rightsize Before Anything Else
&lt;/h2&gt;

&lt;p&gt;Pull 30 days of CPU and memory utilization data. Any instance averaging below 20% CPU with 40%+ memory headroom is a rightsizing candidate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS CLI quickstart:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find all EC2 instances and their current types&lt;/span&gt;
aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Then check CPU utilization via CloudWatch for each ID&lt;/span&gt;
aws cloudwatch get-metric-statistics &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/EC2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric-name&lt;/span&gt; CPUUtilization &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dimensions&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;InstanceId,Value&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;INSTANCE_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--start-time&lt;/span&gt; 2026-05-01T00:00:00Z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--end-time&lt;/span&gt; 2026-05-29T00:00:00Z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--period&lt;/span&gt; 86400 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--statistics&lt;/span&gt; Average
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Cost Explorer's rightsizing recommendations do this automatically with a single click. GCP Active Assist and Azure Advisor are equivalent. Rightsizing typically delivers &lt;strong&gt;15–25% compute savings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For Kubernetes, use &lt;a href="https://www.opencost.io/" rel="noopener noreferrer"&gt;OpenCost&lt;/a&gt; — it's free, CNCF-sandbox, and gives you per-pod cost visibility that native cloud consoles miss entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tactic 2 — Stop Running Steady-State Workloads at On-Demand Prices
&lt;/h2&gt;

&lt;p&gt;Reserved Instances and Savings Plans deliver 40–72% savings vs on-demand for the same compute. If a service has been running continuously for 3+ months, you should not be paying on-demand for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison: AWS commitment options&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Savings vs On-Demand&lt;/th&gt;
&lt;th&gt;Flexibility&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-Year Reserved Instance&lt;/td&gt;
&lt;td&gt;~40%&lt;/td&gt;
&lt;td&gt;Low (instance-locked)&lt;/td&gt;
&lt;td&gt;Known, stable workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-Year Reserved Instance&lt;/td&gt;
&lt;td&gt;~62–72%&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Long-term steady state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute Savings Plan (1yr)&lt;/td&gt;
&lt;td&gt;~40%&lt;/td&gt;
&lt;td&gt;High (any family/region)&lt;/td&gt;
&lt;td&gt;Evolving architectures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spot Instances&lt;/td&gt;
&lt;td&gt;60–90%&lt;/td&gt;
&lt;td&gt;Very high (interruptible)&lt;/td&gt;
&lt;td&gt;Batch, CI/CD jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with Compute Savings Plans unless you know exactly which instance types you'll need for the next 3 years — you almost certainly don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tactic 3 — Schedule Non-Prod Environments to Stop at Night
&lt;/h2&gt;

&lt;p&gt;Your dev/staging/test environments do not need to run at 2 AM on Saturday. Scheduling them to stop outside business hours reduces those environment costs by 65–70%.&lt;/p&gt;

&lt;p&gt;For AWS, the Instance Scheduler is the native option. For Kubernetes, combine HPA with scheduled scale-to-zero on non-production namespaces. Add a Slack &lt;code&gt;/wakeup staging&lt;/code&gt; command via a simple Lambda so engineers can spin up on demand without leaving things running permanently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FinOps Foundation benchmark:&lt;/strong&gt; Teams that implement environment scheduling see 10–20% reduction in total cloud spend. It's the easiest win with the least technical risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tactic 4 — Audit Storage and Snapshots
&lt;/h2&gt;

&lt;p&gt;Storage waste is invisible until you look for it. Three areas consistently surface quick wins:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unattached EBS volumes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 describe-volumes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;status,Values&lt;span class="o"&gt;=&lt;/span&gt;available &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Volumes[*].[VolumeId,Size,CreateTime]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any volume in &lt;code&gt;available&lt;/code&gt; state isn't attached to anything. Delete or snapshot-and-delete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshot retention:&lt;/strong&gt; Set a maximum 30-day retention policy for non-critical snapshots. Older snapshots should move to cheaper tiers. Most teams find they're keeping hundreds of snapshots that serve no real disaster recovery purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 lifecycle policies:&lt;/strong&gt; Data not accessed in 90 days → Infrequent Access. Data older than 180 days → Glacier. This alone can cut S3 costs 30–40% for data-heavy workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tactic 5 — Tag Everything, Then Enforce It
&lt;/h2&gt;

&lt;p&gt;Without cost allocation tags, nobody owns the bill. The minimal effective tag set: &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt; (prod/staging/dev), &lt;code&gt;cost-center&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Enforce tagging at provisioning with AWS Service Control Policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ec2:RunInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rds:CreateDBInstance"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Null"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws:RequestedRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws:ResourceTag/team"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aws:ResourceTag/env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once tagging is clean, a cost-per-team dashboard changes behavior faster than any top-down mandate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool Comparison: Native vs Third-Party
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Multi-Cloud?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Cost Explorer&lt;/td&gt;
&lt;td&gt;AWS-native rightsizing, reservations&lt;/td&gt;
&lt;td&gt;Free (+ $0.01/API call)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCP Active Assist&lt;/td&gt;
&lt;td&gt;GCP idle resource detection&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCost&lt;/td&gt;
&lt;td&gt;Kubernetes cost allocation&lt;/td&gt;
&lt;td&gt;Free (open source)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudHealth (Broadcom)&lt;/td&gt;
&lt;td&gt;Large enterprise multi-cloud&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAST AI&lt;/td&gt;
&lt;td&gt;Kubernetes rightsizing + automation&lt;/td&gt;
&lt;td&gt;Freemium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubecost&lt;/td&gt;
&lt;td&gt;Kubernetes cost + CNCF-compatible&lt;/td&gt;
&lt;td&gt;Freemium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Results You Can Expect
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tactic&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;th&gt;Typical Savings&lt;/th&gt;
&lt;th&gt;Time to Results&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rightsizing&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;15–25% compute&lt;/td&gt;
&lt;td&gt;2–4 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservations / Savings Plans&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;40–72% on committed spend&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment scheduling&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;10–20% total spend&lt;/td&gt;
&lt;td&gt;1 billing cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage cleanup&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;5–15% total spend&lt;/td&gt;
&lt;td&gt;1 billing cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tagging governance&lt;/td&gt;
&lt;td&gt;High (setup)&lt;/td&gt;
&lt;td&gt;Enables all others&lt;/td&gt;
&lt;td&gt;30–60 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FinOps Foundation data shows that mature programs reduce total cloud waste to 15–20% — roughly half the 32–40% baseline. You won't eliminate waste entirely, but getting from 35% to 18% on a $1M budget is $170K back in engineering budget per year.&lt;/p&gt;




&lt;p&gt;If you want the full guide with deeper dives on commitment strategy and FinOps culture, the original post is on &lt;a href="https://lucas8.com/cloud-cost-optimization-best-practices" rel="noopener noreferrer"&gt;lucas8.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>aws</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
