<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Security on ErrorZap</title><link>https://errorzap.com/tags/security/</link><description>Recent content in Security on ErrorZap</description><image><title>ErrorZap</title><url>https://errorzap.com/og.png</url><link>https://errorzap.com/og.png</link></image><generator>Hugo</generator><language>en-US</language><copyright>ErrorZap</copyright><lastBuildDate>Wed, 03 Jun 2026 00:00:00 -0600</lastBuildDate><atom:link href="https://errorzap.com/tags/security/index.xml" rel="self" type="application/rss+xml"/><item><title>I Found My Own Server on Shodan</title><link>https://errorzap.com/posts/i-found-my-own-server-on-shodan/</link><pubDate>Wed, 03 Jun 2026 00:00:00 -0600</pubDate><guid>https://errorzap.com/posts/i-found-my-own-server-on-shodan/</guid><description>I scanned my own infrastructure like an attacker would — and found an admin panel wide open that my firewall swore was closed.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="I Found My Own Server on Shodan" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>It started as a slow-afternoon habit: scan yourself the way an attacker would. Pull up Shodan, type in your own public IP, and brace.</p>
<p>I expected a clean sheet. UFW was configured. <code>ufw deny</code> on everything that wasn&rsquo;t 80, 443, and the VPN. I&rsquo;d checked it a dozen times. Green across the board.</p>
<p>Shodan disagreed.</p>
<p>There it was, indexed and timestamped: an admin/management panel answering on a port that was supposed to be firewalled into oblivion. Banner, title, the works. Anyone with a browser could&rsquo;ve found it. Some of them probably had.</p>
<h2 id="the-investigation">The investigation</h2>
<p>First reaction: Shodan&rsquo;s cache is stale. It happens. So I scanned from the outside, from a box that had no business reaching anything internal.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nmap -Pn -p <span style="color:#bd93f9">9000</span> 192.0.2.10 --open   <span style="color:#6272a4"># from a cloud box, not my network</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># (really: nmap -Pn -p 9000 &lt;my-public-ip&gt;)</span>
</span></span><span style="display:flex;"><span>nmap -Pn -p <span style="color:#bd93f9">9000</span> 192.0.2.10
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>PORT     STATE  SERVICE
</span></span><span style="display:flex;"><span>9000/tcp open   http
</span></span></code></pre></div><p>Open. From the public internet. Not stale.</p>
<p>So I SSH&rsquo;d in and asked UFW what it thought it was doing.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw status verbose | grep <span style="color:#bd93f9">9000</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># (nothing — port 9000 is not allowed)</span>
</span></span><span style="display:flex;"><span>sudo ufw status | head
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Status: active</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 22/tcp   ALLOW   Anywhere</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 443/tcp  ALLOW   Anywhere</span>
</span></span></code></pre></div><p>UFW was adamant: 9000 is closed. The internet was equally adamant: 9000 is open. One of them was lying, and firewalls don&rsquo;t lie. They just get bypassed.</p>
<h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>Then I remembered what was actually listening on 9000.</p>
<p>A Docker container.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker ps --format <span style="color:#f1fa8c">&#39;{{.Names}}\t{{.Ports}}&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># admin-panel   0.0.0.0:9000-&gt;9000/tcp</span>
</span></span></code></pre></div><p><code>0.0.0.0:9000-&gt;9000/tcp</code>. There it is. That <code>-p 9000:9000</code> in the compose file. Docker doesn&rsquo;t ask UFW for permission — it writes its <strong>own</strong> iptables rules, straight into the <code>DOCKER</code> chain, and they get evaluated before your tidy UFW <code>INPUT</code> rules ever run.</p>
<p>Your <code>ufw deny</code> is a polite note on the front door. Docker built a second door around back and propped it open.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>  Inbound packet → :9000
</span></span><span style="display:flex;"><span>        │
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>  ┌───────────────────────────┐
</span></span><span style="display:flex;"><span>  │ iptables nat/PREROUTING   │
</span></span><span style="display:flex;"><span>  │  DOCKER chain (DNAT)  ◄────┼── Docker put this here.
</span></span><span style="display:flex;"><span>  │  → 172.17.0.2:9000        │     UFW never gets a vote.
</span></span><span style="display:flex;"><span>  └───────────────────────────┘
</span></span><span style="display:flex;"><span>        │            ╲
</span></span><span style="display:flex;"><span>        ▼             ╲ (never reached)
</span></span><span style="display:flex;"><span>   container :9000     ┌────────────────────┐
</span></span><span style="display:flex;"><span>                       │ ufw-user chain     │
</span></span><span style="display:flex;"><span>                       │  DENY 9000  (moot) │
</span></span><span style="display:flex;"><span>                       └────────────────────┘
</span></span></code></pre></div><figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Docker port publishing > host firewall (DNAT wins & UFW is skipped)">
  <rect x="0" y="0" width="560" height="220" rx="10" fill="#11111b"/>
  <text x="280" y="30" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="15">Why the firewall didn't matter</text>
<p><text x="40" y="120" text-anchor="middle" fill="#f38ba8" font-family="monospace" font-size="24">☠</text>
<text x="40" y="145" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="11">internet</text></p>
  <line x1="62" y1="110" x2="150" y2="110" stroke="#f38ba8" stroke-width="2" marker-end="url(#a)"/>
  <rect x="150" y="70" width="150" height="80" rx="8" fill="#1e1e2e" stroke="#45475a"/>
  <text x="225" y="100" text-anchor="middle" fill="#fab387" font-family="monospace" font-size="13">DOCKER chain</text>
  <text x="225" y="122" text-anchor="middle" fill="#a6e3a1" font-family="monospace" font-size="12">DNAT :9000 & DNAT wins</text>
  <text x="225" y="140" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="10">first < ufw, host rule > skipped</text>
  <rect x="150" y="165" width="150" height="40" rx="8" fill="#1e1e2e" stroke="#313244"/>
  <text x="225" y="190" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="12">ufw DENY 9000 (moot)</text>
  <line x1="300" y1="110" x2="400" y2="110" stroke="#89b4fa" stroke-width="2" marker-end="url(#b)"/>
  <rect x="400" y="80" width="130" height="60" rx="8" fill="#313244" stroke="#cba6f7"/>
  <text x="465" y="108" text-anchor="middle" fill="#cba6f7" font-family="monospace" font-size="13">admin-panel</text>
  <text x="465" y="127" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="11">172.17.0.2:9000</text>
  <defs>
    <marker id="a" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#f38ba8"/></marker>
    <marker id="b" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#89b4fa"/></marker>
  </defs>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">Docker's DNAT rule wins the race. UFW's deny never gets to vote.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p>The publish target was the bug. <code>0.0.0.0</code> means &ldquo;the whole world.&rdquo; It almost never should.</p>
<p>I bound the panel to loopback only and put it behind the reverse proxy with auth, reachable just through the VPN.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#6272a4"># docker-compose.yml — before</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f1fa8c">&#34;9000:9000&#34;</span>        <span style="color:#6272a4"># = 0.0.0.0:9000, published to the internet</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># after</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f1fa8c">&#34;127.0.0.1:9000:9000&#34;</span>   <span style="color:#6272a4"># localhost only; proxy/VPN reaches it</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose up -d
</span></span><span style="display:flex;"><span>docker ps --format <span style="color:#f1fa8c">&#39;{{.Names}}\t{{.Ports}}&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># admin-panel   127.0.0.1:9000-&gt;9000/tcp</span>
</span></span></code></pre></div><p>For anything that genuinely must survive the firewall, lock it at the <code>DOCKER-USER</code> chain — the one place Docker promises not to clobber:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># allow only the tailnet, drop everyone else for published container ports</span>
</span></span><span style="display:flex;"><span>sudo iptables -I DOCKER-USER -i eth0 -s 100.64.0.0/10 -j RETURN
</span></span><span style="display:flex;"><span>sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport <span style="color:#bd93f9">9000</span> -j DROP
</span></span></code></pre></div><p>Re-scan from outside:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nmap -Pn -p <span style="color:#bd93f9">9000</span> 192.0.2.10
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># PORT     STATE    SERVICE</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 9000/tcp filtered http</span>
</span></span></code></pre></div><p><code>filtered</code>. Dark. The panel only answers behind the tunnel now.</p>
<h2 id="why-it-happened">Why it happened</h2>
<p>Nobody screwed up the firewall. The firewall did exactly what it was told. The trap is the mental model: people assume UFW sits at the front gate and inspects everything inbound. Docker doesn&rsquo;t go through that gate — it cuts its own keyhole in <code>iptables</code> and your host rules never see the packet.</p>
<p><code>-p 9000:9000</code> reads like &ldquo;expose this locally.&rdquo; It actually means &ldquo;publish this to <code>0.0.0.0</code>.&rdquo; One missing <code>127.0.0.1:</code> prefix and an internal admin tool was on the public internet for who-knows-how-long, indexed by Shodan, waiting.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>Scan yourself the way an attacker would.</strong> Shodan plus an external <code>nmap</code> from a box outside your network is the only ground truth. Your config file is a hypothesis, not a result.</li>
<li><strong>Docker port publishing ignores your host firewall.</strong> <code>-p PORT:PORT</code> writes its own iptables rules and bypasses UFW entirely. <code>ufw deny</code> does nothing for published container ports.</li>
<li><strong>Bind admin services to <code>127.0.0.1</code> or a VPN, never <code>0.0.0.0</code>.</strong> Always prefix the publish: <code>127.0.0.1:9000:9000</code>. Default-public is a footgun.</li>
<li><strong>Use Docker-aware firewalling.</strong> If a container port truly needs outside reach, gate it in the <code>DOCKER-USER</code> chain — the one chain Docker won&rsquo;t overwrite.</li>
<li><strong>Admin panels belong on localhost or a tunnel, behind a reverse proxy and auth.</strong> Convenient-but-public is just public.</li>
</ul>
]]></content:encoded></item><item><title>The Smart Blind That Took Down an Entire Network</title><link>https://errorzap.com/posts/the-smart-blind-that-took-down-the-network/</link><pubDate>Thu, 28 May 2026 00:00:00 -0600</pubDate><guid>https://errorzap.com/posts/the-smart-blind-that-took-down-the-network/</guid><description>A $30 Wi-Fi window blind decided it was the default gateway, and the whole site went dark whenever it felt like it.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="The Smart Blind That Took Down an Entire Network" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>The ticket said &ldquo;internet is down.&rdquo; Then, four minutes later, &ldquo;never mind, it&rsquo;s back.&rdquo; Then, twenty minutes after that, &ldquo;it&rsquo;s down again.&rdquo;</p>
<p>That pattern is a special kind of hell. A clean outage you can chase. A flapping one that heals itself before you SSH in just laughs at you.</p>
<p>I got a client on the phone. Their whole site would blackhole — every desktop, every phone, the printer, all of it — for one to five minutes, then snap back like nothing happened. No schedule. No trigger anyone could name. The ISP swore the circuit was clean, and for once the ISP was right.</p>
<h2 id="the-investigation">The investigation</h2>
<p>I started where you start: at the edge. The gateway was up. WAN was up. The firewall logs were boring in the way you <em>want</em> them to be boring. No flapping interface, no CPU spike, no DHCP storm in the leases.</p>
<p>So I sat on a workstation and just hammered the gateway while I waited for the next outage.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ping -i 0.5 192.0.2.1
</span></span></code></pre></div><p>It ran fine for ten minutes. Then — dead air. Eight, nine, ten dropped replies. Total blackhole. Then it came back on its own.</p>
<p>The instant it died, I dumped the ARP table.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>arp -a 192.0.2.1
</span></span></code></pre></div><p>And there it was. The gateway&rsquo;s IP was mapped to a MAC address I did not recognize. Not the firewall&rsquo;s MAC. Some OUI I had to look up by hand.</p>
<p>I watched it flap in real time:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>┌─────────────────────────────────────────────────────────────┐
</span></span><span style="display:flex;"><span>│  arp -a 192.0.2.1   (watched over ~30s)                      │
</span></span><span style="display:flex;"><span>├─────────────────────────────────────────────────────────────┤
</span></span><span style="display:flex;"><span>│  t+0s   192.0.2.1  ►  aa:bb:cc:11:22:33   (firewall)   ✓     │
</span></span><span style="display:flex;"><span>│  t+6s   192.0.2.1  ►  de:ad:be:ef:00:99   (???)        ✗     │
</span></span><span style="display:flex;"><span>│  t+9s   192.0.2.1  ►  de:ad:be:ef:00:99   (???)        ✗     │
</span></span><span style="display:flex;"><span>│  t+14s  192.0.2.1  ►  aa:bb:cc:11:22:33   (firewall)   ✓     │
</span></span><span style="display:flex;"><span>│  t+19s  192.0.2.1  ►  de:ad:be:ef:00:99   (???)        ✗     │
</span></span><span style="display:flex;"><span>└─────────────────────────────────────────────────────────────┘
</span></span><span style="display:flex;"><span>         ▼ when the IP pointed at de:ad:..:99, traffic vanished
</span></span></code></pre></div><p>When the gateway IP pointed at the firewall, the network worked. When it pointed at that mystery MAC, every packet bound for the internet got handed to a device that did absolutely nothing with it. Classic ARP poisoning. The only question was <em>who</em>.</p>
<h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>I looked up the OUI. The mystery MAC belonged to a Tuya-based Wi-Fi module — the kind that ships inside cheap smart-home gadgets by the millions.</p>
<p>Then I cross-referenced the DHCP leases for that MAC and found a hostname that made me laugh out loud: a smart <strong>window blind</strong> controller. Somebody had put a $30 motorized blind on the corporate flat network.</p>
<p>That little NIC was broadcasting unsolicited ARP replies claiming to be <code>192.0.2.1</code>. Every client on the segment believed it, updated its ARP cache, and started shipping its default route straight into a window covering.</p>
<h2 id="the-fix">The fix</h2>
<p>First, stop the bleeding. I pinned the gateway&rsquo;s real MAC on the affected machines so they&rsquo;d stop trusting the lie while I worked.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Linux</span>
</span></span><span style="display:flex;"><span>sudo ip neigh replace 192.0.2.1 lladdr aa:bb:cc:11:22:33 dev eth0 nud permanent
</span></span></code></pre></div><p>Then the real fix — get IoT off the flat network entirely and turn on the switch protections that should&rsquo;ve been on from day one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>BEFORE                              AFTER
</span></span><span style="display:flex;"><span>──────                              ─────
</span></span><span style="display:flex;"><span>VLAN 1 (everything)                 VLAN 10  desktops/servers
</span></span><span style="display:flex;"><span>  ├─ desktops                       VLAN 20  voice
</span></span><span style="display:flex;"><span>  ├─ servers                        VLAN 90  IoT  ◄── blind lives here,
</span></span><span style="display:flex;"><span>  ├─ phones                                          firewalled, no L2
</span></span><span style="display:flex;"><span>  └─ smart blind  ◄── poisoner                       reach to clients
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Cisco-style switch hardening (concept)</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Trust only the uplink for DHCP; drop rogue offers elsewhere</span>
</span></span><span style="display:flex;"><span>ip dhcp snooping
</span></span><span style="display:flex;"><span>ip dhcp snooping vlan <span style="color:#bd93f9">90</span>
</span></span><span style="display:flex;"><span>interface Gi1/0/24
</span></span><span style="display:flex;"><span> ip dhcp snooping trust
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Dynamic ARP Inspection — validate ARP against snooping bindings</span>
</span></span><span style="display:flex;"><span>ip arp inspection vlan <span style="color:#bd93f9">90</span>
</span></span><span style="display:flex;"><span>interface Gi1/0/24
</span></span><span style="display:flex;"><span> ip arp inspection trust
</span></span></code></pre></div><figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A smart blind impersonating the gateway in the ARP table">
<rect x="0" y="0" width="560" height="220" rx="10" fill="#11111b"/>
<rect x="30" y="40" width="130" height="64" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="95" y="68" text-anchor="middle" font-family="monospace" font-size="13" fill="#cdd6f4">Workstation</text>
<text x="95" y="88" text-anchor="middle" font-family="monospace" font-size="11" fill="#6c7086">who has .1?</text>
<rect x="400" y="20" width="130" height="50" rx="8" fill="#1e1e2e" stroke="#a6e3a1"/>
<text x="465" y="42" text-anchor="middle" font-family="monospace" font-size="13" fill="#a6e3a1">Gateway .1</text>
<text x="465" y="59" text-anchor="middle" font-family="monospace" font-size="10" fill="#6c7086">aa:bb:cc...</text>
<rect x="400" y="140" width="130" height="56" rx="8" fill="#1e1e2e" stroke="#f38ba8"/>
<text x="465" y="164" text-anchor="middle" font-family="monospace" font-size="12" fill="#f38ba8">Smart Blind</text>
<text x="465" y="181" text-anchor="middle" font-family="monospace" font-size="10" fill="#fab387">"I am .1!"</text>
<line x1="160" y1="64" x2="400" y2="45" stroke="#45475a" stroke-width="2" stroke-dasharray="5 4"/>
<line x1="160" y1="80" x2="400" y2="160" stroke="#f38ba8" stroke-width="2.5"/>
<text x="270" y="120" text-anchor="middle" font-family="monospace" font-size="11" fill="#f38ba8">poisoned ARP ►</text>
<text x="280" y="210" text-anchor="middle" font-family="monospace" font-size="11" fill="#cba6f7">traffic blackholes into a window covering</text>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">The blind announces itself as the gateway; clients believe it and route to nowhere.</figcaption>
</figure>
<p>Last step: I yanked the blind, pushed its vendor firmware update, and parked it on the isolated IoT VLAN where it can lie about being the gateway all it wants — and nobody who matters will hear it.</p>
<h2 id="why-it-happened">Why it happened</h2>
<p>Cheap Tuya firmware is a grab bag. Some builds have buggy network stacks that emit malformed or unsolicited ARP — gratuitous ARP gone feral, sometimes triggered by a Wi-Fi reconnect, which explains the random timing. Could also be straight-up malicious; with no-name IoT you genuinely can&rsquo;t tell, and it doesn&rsquo;t change the remediation.</p>
<p>Either way, a flat L2 network <em>trusts every device equally</em>. ARP has no authentication. Whatever shouts loudest and last, wins. Put one mouthy gadget on that wire and it can pretend to be anything — including the door to the internet.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>On weird, intermittent, self-healing outages, check the gateway&rsquo;s ARP entry FIRST.</strong> <code>arp -a &lt;gateway-ip&gt;</code> flapping to an unknown MAC is a five-second smoking gun.</li>
<li><strong>Match a rogue MAC to its OUI and your DHCP leases.</strong> That&rsquo;s how a &ldquo;mystery device&rdquo; became &ldquo;the smart blind in the break room.&rdquo;</li>
<li><strong>IoT does not belong on your flat network. Ever.</strong> Its own VLAN/SSID, firewalled off from clients, no L2 reach to anything that matters.</li>
<li><strong>Turn on DHCP Snooping and Dynamic ARP Inspection.</strong> They exist precisely to kill rogue gateways and ARP lies at the switch.</li>
<li><strong>Cheap IoT is an untrusted host by default</strong> — bug or malice, you treat it the same: isolate, update, and never let it speak for the gateway.</li>
</ul>
]]></content:encoded></item><item><title>I Found a Secret in a Git Repo (It Was Mine)</title><link>https://errorzap.com/posts/i-found-a-secret-in-a-git-repo/</link><pubDate>Thu, 21 May 2026 00:00:00 -0600</pubDate><guid>https://errorzap.com/posts/i-found-a-secret-in-a-git-repo/</guid><description>I went looking for a leaked credential in a client&amp;rsquo;s stack and found one staring back at me — committed by my own hand, baked into history forever.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="I Found a Secret in a Git Repo (It Was Mine)" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>It was supposed to be a quick audit.</p>
<p>A client had asked me to sweep a private repo before they handed access to a new contractor. Routine. I cloned it, ran a scanner over the working tree, and started reading. Five minutes in, the scanner lit up red on a config helper.</p>
<p>A reusable credential. Plaintext. In the repo.</p>
<p>I felt that familiar little spike of righteous sysadmin adrenaline — <em>who the hell committed THIS</em> — opened the blame, and read the author line.</p>
<p>It was me.</p>
<h2 id="the-investigation">The investigation</h2>
<p>Here&rsquo;s the part everyone gets wrong, and the part I almost got wrong too.</p>
<p>My first instinct was to delete the line, commit &ldquo;remove secret,&rdquo; and move on. Crisis averted, right?</p>
<p>Wrong. The file in the working tree is the <em>least</em> important place that secret lives. Git is a time machine. Deleting it today does nothing about the dozens of commits where it sits, fat and happy, in the history. Anyone with <code>git log</code> and ten seconds can walk straight to it.</p>
<p>I checked how deep it went:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git log --all --oneline -S <span style="color:#f1fa8c">&#39;the_secret_string&#39;</span> -- path/to/config
</span></span><span style="display:flex;"><span>git rev-list --all --count
</span></span></code></pre></div><p>It went <em>deep</em>. The credential had been in the repo for months, across dozens of commits, surviving file renames and a refactor. Deleting the file would leave it perfectly intact in every parent commit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>  the working tree (what you see)
</span></span><span style="display:flex;"><span>        │
</span></span><span style="display:flex;"><span>        ▼
</span></span><span style="display:flex;"><span>   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
</span></span><span style="display:flex;"><span>   │ commit  │◄──│ commit  │◄──│ commit  │◄──│ commit  │
</span></span><span style="display:flex;"><span>   │  HEAD   │   │  ~12    │   │  ~31    │   │  ~40    │
</span></span><span style="display:flex;"><span>   │  ✗ gone │   │ ▓ secret│   │ ▓ secret│   │ ▓ secret│
</span></span><span style="display:flex;"><span>   └─────────┘   └─────────┘   └─────────┘   └─────────┘
</span></span><span style="display:flex;"><span>        ▲              └──────────────┬──────────────┘
</span></span><span style="display:flex;"><span>   you &#34;fixed&#34; it          still right here, forever
</span></span></code></pre></div><h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>The realization isn&rsquo;t &ldquo;I need to scrub this.&rdquo; It&rsquo;s earlier and uglier than that:</p>
<p><strong>A secret is compromised the instant it&rsquo;s committed.</strong> Not when the repo goes public. Not when someone clones it. At commit time. Every backup, every fork, every laptop that ever pulled, every CI cache — assume the secret is <em>out</em>. You cannot un-ring that bell by editing history.</p>
<p>So scrubbing history is necessary, but it is not step one. Step one is to make the leaked value worthless.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Remediation order diagram">
  <rect x="0" y="0" width="560" height="220" fill="#11111b" rx="10"/>
  <text x="280" y="30" fill="#cdd6f4" font-family="monospace" font-size="15" text-anchor="middle">Remediation, in the only order that works</text>
  <rect x="24" y="58" width="118" height="100" rx="8" fill="#1e1e2e" stroke="#f38ba8" stroke-width="2"/>
  <text x="83" y="86" fill="#f38ba8" font-family="monospace" font-size="22" text-anchor="middle">1</text>
  <text x="83" y="112" fill="#cdd6f4" font-family="monospace" font-size="12" text-anchor="middle">ROTATE</text>
  <text x="83" y="132" fill="#6c7086" font-family="monospace" font-size="10" text-anchor="middle">kill the value</text>
  <rect x="156" y="58" width="118" height="100" rx="8" fill="#1e1e2e" stroke="#fab387" stroke-width="2"/>
  <text x="215" y="86" fill="#fab387" font-family="monospace" font-size="22" text-anchor="middle">2</text>
  <text x="215" y="112" fill="#cdd6f4" font-family="monospace" font-size="12" text-anchor="middle">SCRUB</text>
  <text x="215" y="132" fill="#6c7086" font-family="monospace" font-size="10" text-anchor="middle">purge history</text>
  <rect x="288" y="58" width="118" height="100" rx="8" fill="#1e1e2e" stroke="#89b4fa" stroke-width="2"/>
  <text x="347" y="86" fill="#89b4fa" font-family="monospace" font-size="22" text-anchor="middle">3</text>
  <text x="347" y="112" fill="#cdd6f4" font-family="monospace" font-size="12" text-anchor="middle">RELOCATE</text>
  <text x="347" y="132" fill="#6c7086" font-family="monospace" font-size="10" text-anchor="middle">secrets mgr</text>
  <rect x="420" y="58" width="118" height="100" rx="8" fill="#1e1e2e" stroke="#a6e3a1" stroke-width="2"/>
  <text x="479" y="86" fill="#a6e3a1" font-family="monospace" font-size="22" text-anchor="middle">4</text>
  <text x="479" y="112" fill="#cdd6f4" font-family="monospace" font-size="12" text-anchor="middle">PREVENT</text>
  <text x="479" y="132" fill="#6c7086" font-family="monospace" font-size="10" text-anchor="middle">pre-commit scan</text>
<p><text x="149" y="113" fill="#45475a" font-family="monospace" font-size="18" text-anchor="middle">►</text>
<text x="281" y="113" fill="#45475a" font-family="monospace" font-size="18" text-anchor="middle">►</text>
<text x="413" y="113" fill="#45475a" font-family="monospace" font-size="18" text-anchor="middle">►</text></p>
<p><text x="280" y="195" fill="#cba6f7" font-family="monospace" font-size="12" text-anchor="middle">skip step 1 &amp; steps 2-4 are theater</text>
</svg></p>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">Deleting the file is not on this list. It was never remediation.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p><strong>1. Rotate, immediately.</strong> Before anything else, I logged into the provider, revoked the credential, and issued a fresh one. The old value is now a dead string. Whoever has it has nothing.</p>
<p><strong>2. Purge it from history.</strong> With the value already dead, I scrubbed it so it stops scaring the next auditor (me, in six months). <code>git filter-repo</code> is the modern tool; BFG works too.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># install once: pip install git-filter-repo</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#39;the_secret_string==&gt;REDACTED&#39;</span> &gt; replacements.txt
</span></span><span style="display:flex;"><span>git filter-repo --replace-text replacements.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># then force the rewritten history out</span>
</span></span><span style="display:flex;"><span>git push --force --all
</span></span><span style="display:flex;"><span>git push --force --tags
</span></span></code></pre></div><p>Every collaborator re-clones after this. A force-push that rewrites history will wreck anyone&rsquo;s local copy — warn them first.</p>
<p><strong>3. Move secrets out of the repo for good.</strong> The config now reads from the environment, injected at runtime from a secrets manager. Nothing sensitive touches the tree.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># .env stays OUT of git</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#39;.env&#39;</span> &gt;&gt; .gitignore
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># app reads from the environment, value lives in the secrets manager</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">API_TOKEN</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;</span><span style="color:#ff79c6">$(</span>vault kv get -field<span style="color:#ff79c6">=</span>token secret/app<span style="color:#ff79c6">)</span><span style="color:#f1fa8c">&#34;</span>
</span></span></code></pre></div><p><strong>4. Make it impossible to repeat.</strong> A pre-commit hook scans every staged change so a secret never reaches a commit in the first place.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># .pre-commit-config.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#  - repo: https://github.com/gitleaks/gitleaks</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    rev: v8.18.0</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    hooks: [{ id: gitleaks }]</span>
</span></span><span style="display:flex;"><span>pre-commit install
</span></span><span style="display:flex;"><span>gitleaks detect --source . --verbose
</span></span></code></pre></div><h2 id="why-it-happened">Why it happened</h2>
<p>No villain here. Just the most ordinary mistake in the trade.</p>
<p>Months ago, mid-deploy, I needed the thing to <em>work now</em>. I hardcoded the credential &ldquo;just to test,&rdquo; told myself I&rsquo;d pull it out before committing, got interrupted, and committed everything with a <code>git add .</code>. The TODO never came back. The repo was private, so it felt safe — and &ldquo;private&rdquo; is exactly the lie that lets these things rot in history for half a year.</p>
<p>Private isn&rsquo;t a control. It&rsquo;s a setting someone can flip.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>A secret is burned the moment it&rsquo;s committed.</strong> Rotate first, always. Treat the value as public from commit time forward — because effectively it is.</li>
<li><strong>Deleting the file is not remediation.</strong> The credential lives in every old commit. You must rewrite history (<code>git filter-repo</code> / BFG) and force-push, or it&rsquo;s still there.</li>
<li><strong>Secrets belong in a manager, not the tree.</strong> Env-injected values, <code>.gitignore</code> your <code>.env</code>, and never let credentials and code share a home.</li>
<li><strong>Automate the catch.</strong> A <code>gitleaks</code>/<code>trufflehog</code> pre-commit hook stops the next leak before it&rsquo;s a commit — humans forget, hooks don&rsquo;t.</li>
<li><strong>&ldquo;Private repo&rdquo; is not a security boundary.</strong> Audit your own repos like you&rsquo;d audit a stranger&rsquo;s. The author line might surprise you.</li>
</ul>
]]></content:encoded></item><item><title>The Subdomain That Pointed at Nothing</title><link>https://errorzap.com/posts/the-subdomain-that-pointed-at-nothing/</link><pubDate>Tue, 12 May 2026 00:00:00 -0600</pubDate><guid>https://errorzap.com/posts/the-subdomain-that-pointed-at-nothing/</guid><description>A routine DNS audit turned up subdomains aimed at servers that died months ago — quiet little doors anyone could walk through.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="The Subdomain That Pointed at Nothing" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>It started with a boring task. Quarterly DNS audit. Coffee, a zone file, and a vague sense that everything was fine.</p>
<p>It was not fine.</p>
<p>Halfway down the export I hit a CNAME — a customer-facing subdomain — pointing at a cloud host we&rsquo;d decommissioned back in the winter. The instance was gone. The DNS record had never gotten the memo.</p>
<p>I sat there for a second. That subdomain still resolved. It still answered. It just answered <em>nothing</em> — a hosting account that no longer existed, on infrastructure we&rsquo;d long since stopped paying for.</p>
<p>That&rsquo;s not a typo. That&rsquo;s a loaded gun pointed at your own brand.</p>
<h2 id="the-investigation">The investigation</h2>
<p>A dangling DNS record is a pointer with no backing store. The record says &ldquo;this name lives over there.&rdquo; But &ldquo;over there&rdquo; got freed months ago — the VM was destroyed, the cloud bucket released, the hosting account closed.</p>
<p>The danger isn&rsquo;t that it&rsquo;s broken. The danger is that the resource is now <em>available for anyone to claim</em>.</p>
<p>I pulled the full zone and started checking targets one by one. Does the A record&rsquo;s IP still belong to us? Is the CNAME target a hostname we still control? Or is it a now-orphaned cloud resource sitting in a provider&rsquo;s free pool, waiting for the next person to grab it?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>        Your DNS                      The Internet
</span></span><span style="display:flex;"><span>   ┌──────────────────┐         ┌────────────────────────┐
</span></span><span style="display:flex;"><span>   │ app.example.com  │         │  cloud host (DELETED)   │
</span></span><span style="display:flex;"><span>   │  CNAME ──────────┼────────▶│  account closed, freed  │
</span></span><span style="display:flex;"><span>   └──────────────────┘         │  ▒▒▒ up for grabs ▒▒▒  │
</span></span><span style="display:flex;"><span>                                └───────────┬────────────┘
</span></span><span style="display:flex;"><span>                                            │  attacker
</span></span><span style="display:flex;"><span>                                            ▼  re-claims it
</span></span><span style="display:flex;"><span>                                ┌────────────────────────┐
</span></span><span style="display:flex;"><span>                                │  serves THEIR content   │
</span></span><span style="display:flex;"><span>                                │  on YOUR subdomain      │
</span></span><span style="display:flex;"><span>                                └────────────────────────┘
</span></span></code></pre></div><h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>Here&rsquo;s the part that turns a stale record into a breach.</p>
<p>If an attacker registers that freed cloud resource — same provider, same dangling target — the provider hands <em>them</em> the keys. Now your subdomain serves their content. And because it&rsquo;s <em>your</em> subdomain, the blast radius is ugly:</p>
<ul>
<li>Pixel-perfect phishing on a domain your users already trust.</li>
<li>Cookies scoped to <code>*.example.com</code> get harvested — including session cookies from your real apps.</li>
<li>A free, fully valid TLS certificate, because the takeover host can pass the domain-control check for a name you literally pointed at it.</li>
</ul>
<p>No CVE. No exploit chain. Just a DNS record that outlived its server.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Subdomain takeover flow">
  <rect x="0" y="0" width="560" height="220" rx="10" fill="#11111b"/>
  <rect x="28" y="74" width="150" height="72" rx="8" fill="#1e1e2e" stroke="#45475a"/>
  <text x="103" y="104" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="13">app.example.com</text>
  <text x="103" y="126" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="11">CNAME ></text>
  <rect x="205" y="74" width="150" height="72" rx="8" fill="#1e1e2e" stroke="#f38ba8"/>
  <text x="280" y="100" text-anchor="middle" fill="#f38ba8" font-family="monospace" font-size="12">freed host</text>
  <text x="280" y="120" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="11">up for grabs</text>
  <rect x="382" y="74" width="150" height="72" rx="8" fill="#1e1e2e" stroke="#cba6f7"/>
  <text x="457" y="100" text-anchor="middle" fill="#cba6f7" font-family="monospace" font-size="12">attacker</text>
  <text x="457" y="120" text-anchor="middle" fill="#fab387" font-family="monospace" font-size="11">re-claims it</text>
  <line x1="178" y1="110" x2="203" y2="110" stroke="#89b4fa" stroke-width="2" marker-end="url(#a)"/>
  <line x1="355" y1="110" x2="380" y2="110" stroke="#f38ba8" stroke-width="2" marker-end="url(#a)"/>
  <text x="280" y="180" text-anchor="middle" fill="#94e2d5" font-family="monospace" font-size="12">valid TLS & your name = phishing on brand</text>
  <defs>
    <marker id="a" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
      <path d="M0,0 L6,3 L0,6 Z" fill="#89b4fa"/>
    </marker>
  </defs>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">A deleted host gets re-claimed — and your trusted subdomain serves someone else's payload.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p>First, inventory everything. Pull the whole zone, not just the records you remember.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Dump every A / AAAA / CNAME in the zone</span>
</span></span><span style="display:flex;"><span>dig @ns1.example.com example.com AXFR <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  | awk <span style="color:#f1fa8c">&#39;$4 ~ /^(A|AAAA|CNAME)$/ {print $1, $4, $5}&#39;</span>
</span></span></code></pre></div><p>Then resolve each one and check whether the target still answers — and whether it&rsquo;s still <em>yours</em>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#ff79c6">while</span> <span style="color:#8be9fd;font-style:italic">read</span> -r name <span style="color:#8be9fd;font-style:italic">type</span> target; <span style="color:#ff79c6">do</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;=== </span><span style="color:#8be9fd;font-style:italic">$name</span><span style="color:#f1fa8c"> (</span><span style="color:#8be9fd;font-style:italic">$type</span><span style="color:#f1fa8c">) -&gt; </span><span style="color:#8be9fd;font-style:italic">$target</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>  dig +short <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$name</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>  curl -sS -o /dev/null -w <span style="color:#f1fa8c">&#34;  http=%{http_code} ip=%{remote_ip}\n&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>    --max-time <span style="color:#bd93f9">8</span> <span style="color:#f1fa8c">&#34;https://</span><span style="color:#8be9fd;font-style:italic">$name</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">||</span> <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;  DEAD / no response&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">done</span> &lt; zone-records.txt
</span></span></code></pre></div><p>For the dangling ones, the rule is simple: if you don&rsquo;t control the target, the record dies or gets repointed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Delete the stale record (RFC 2136 dynamic update)</span>
</span></span><span style="display:flex;"><span>nsupdate -k /etc/dns/update.key <span style="color:#f1fa8c">&lt;&lt;&#39;EOF&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">server ns1.example.com
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">zone example.com
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">update delete app.example.com. CNAME
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">send
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">EOF</span>
</span></span></code></pre></div><p>Repoint anything still in use at infrastructure <em>you</em> own — a host behind your own reverse proxy, not a third-party resource you might let lapse again.</p>
<p>Finally, make it a standing job. A nightly check that flags any record whose target stops resolving or stops belonging to you.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># cron: nightly dangling-DNS sweep, alert on dead targets</span>
</span></span><span style="display:flex;"><span><span style="color:#bd93f9">0</span> <span style="color:#bd93f9">4</span> * * *  /opt/dns/dangle-check.sh <span style="color:#ff79c6">&amp;&amp;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  curl -fsS https://hc-ping.test/dns-sweep
</span></span></code></pre></div><h2 id="why-it-happened">Why it happened</h2>
<p>DNS outlives servers. Always.</p>
<p>When you spin a box down, you delete the box. You raise the ticket, you confirm it&rsquo;s gone, you move on. The DNS record — living in a different system, owned by a different runbook — just sits there pointing at a ghost. Nobody&rsquo;s job ends with &ldquo;and remove the CNAME.&rdquo;</p>
<p>Multiply that by a few years of &ldquo;temporary&rdquo; subdomains and deprovisioned cloud accounts, and you&rsquo;ve got a quiet pile of doors with no locks.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>Inventory every record.</strong> You can&rsquo;t audit a zone you&rsquo;ve only half-remembered. Pull the whole thing.</li>
<li><strong>Verify the target, not just the name.</strong> A record that resolves isn&rsquo;t safe — confirm the thing it points at is still alive <em>and still yours</em>.</li>
<li><strong>Prune the moment a server dies.</strong> Decommissioning a host isn&rsquo;t done until its DNS is gone too. Bake it into the runbook.</li>
<li><strong>Prefer targets you control.</strong> Point subdomains at your own infrastructure, not third-party resources you might forget to renew.</li>
<li><strong>Monitor continuously.</strong> Dangling DNS is created by routine. Catch it with routine — a nightly sweep beats a quarterly surprise.</li>
</ul>
]]></content:encoded></item><item><title>online security gut check. secure yourself now.</title><link>https://errorzap.com/posts/online-security-gut-check-secure/</link><pubDate>Tue, 08 Jul 2025 02:50:00 -0600</pubDate><guid>https://errorzap.com/posts/online-security-gut-check-secure/</guid><description>🔐 Have You Enabled 2FA Everywhere? Changed Your Password This Decade?Let’s start with a gut check.Do you reuse the same password across multiple sites?Is y</description><content:encoded><![CDATA[<p><img loading="lazy" src="/posts/online-security-gut-check-secure/img1.jpg"></p>
<p>🔐 Have You Enabled 2FA Everywhere? Changed Your Password This Decade?</p>
<p>Let’s start with a gut check.</p>
<p>Do you reuse the same password across multiple sites?</p>
<p>Is your email password the same one you used in 2013?</p>
<p>Have you enabled two-factor authentication (2FA) on your most important accounts?</p>
<p>Or&hellip; are you still getting text codes (yikes) instead of using an authenticator app?</p>
<p>If any of the above made you uncomfortable — good. That’s the point.</p>
<hr>
<p>🧠 Why This Still Matters</p>
<p>Every week we clean up the mess after someone gets locked out of an account, or worse, loses access due to a breach. And 99% of the time? It could’ve been prevented with a simple change:</p>
<p>✅ Stronger, unique passwords</p>
<p>✅ Two-Factor Authentication (2FA)</p>
<p>Still think you’re safe because “nobody would hack me”? Newsflash: they don’t target you. They target everyone, and your password is probably already floating around out there. Just check <a href="https://haveibeenpwned.com">https://haveibeenpwned.com</a> if you don’t believe me.</p>
<hr>
<p>🛡️ Do This Now:</p>
<p>Enable 2FA (Everywhere)</p>
<p>Start with:</p>
<ol>
<li>
<p>Email</p>
</li>
<li>
<p>Bank</p>
</li>
<li>
<p>cloud storage</p>
</li>
<li>
<p>Social media</p>
</li>
<li>
<p>Any sites with your personal data or billing info</p>
</li>
</ol>
<p>Use an authenticator app (like Authy, Microsoft Authenticator, or Google Authenticator). Not SMS. Text-based 2FA can be hijacked.</p>
<ol start="2">
<li>Use a Password Manager</li>
</ol>
<p>Bitwarden, 1Password, or even the built-in tools from Apple or Google are far better than your sticky note or Excel sheet.</p>
<ol start="3">
<li>Change Old Passwords</li>
</ol>
<p>If your password is:</p>
<p>Over 5 years old</p>
<p>Shared across more than one site</p>
<p>Or contains your pet&rsquo;s name and birth year&hellip;</p>
<p>Change. It. Now.</p>
<p>Use 12+ characters, mix it up, and stop using &ldquo;!&rdquo; at the end to feel secure. Hackers are wise to that game.</p>
<hr>
<p>⚙️ Bonus Tip: Turn on Login Alerts</p>
<p>Most sites let you enable notifications when someone logs in from a new device. Do that. It’s like a smoke alarm for your accounts.</p>
<p>Final tip: listen to your IT people, ensure you have working backups, and someone monitoring the security of your devices, servers, and other online assets.</p>
<p>And update your end of life networking devices and computers!</p>
<hr>
<p>🔚 Final Thoughts</p>
<p>Cybersecurity isn’t just for IT nerds. It’s for everyone. The smallest effort (like enabling 2FA) goes a long way in preventing a total digital meltdown.</p>
<p>And if it’s too much? That’s what we’re here for.</p>
<p>!Need help securing your accounts or deploying 2FA for your business? Contact us <a href="http://mypueblopc.com">mypueblopc.com</a></p>
]]></content:encoded></item></channel></rss>