{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "The Duke Post",
  "home_page_url": "https://thedukepost.com/",
  "feed_url": "https://thedukepost.com/feed.json",
  "description": "Tech news and signal, curated and written by The Duke Post.",
  "icon": "https://thedukepost.com/icon-192.png",
  "favicon": "https://thedukepost.com/favicon-v3.svg",
  "items": [
    {
      "id": "https://thedukepost.com/articles/mtls-fail-closed-tradeoff",
      "url": "https://thedukepost.com/articles/mtls-fail-closed-tradeoff",
      "title": "The Network Was Never the Boundary: What mTLS Actually Buys Service-to-Service Traffic",
      "summary": "Every internal service already treats \"it's on the same network\" as proof enough. mTLS replaces that assumption with a certificate check on every connection — which is exactly why it works, and exactly why a bad rotation can take down more at once than an attacker ever could.",
      "content_html": "<p>Mutual TLS — mTLS — is TLS with the authentication running in both directions. In ordinary TLS, the client checks the server&#39;s certificate and trusts it&#39;s talking to the real thing; the server doesn&#39;t check anything back. In mTLS, the server checks the client too: it demands a certificate, verifies the chain against a CA it trusts, and confirms the client&#39;s identity, all before the actual request gets processed. No valid client certificate, no connection — regardless of which network the call came from.</p>\n<p>That second check matters more than it sounds like it should, because of what most internal systems do without it. Without mTLS, &quot;reachable&quot; and &quot;trusted&quot; are usually the same thing: if a request can get to Service B&#39;s port at all, Service B mostly assumes it&#39;s legitimate, because reaching it means being inside the same VPC or cluster. That&#39;s a weaker guarantee than it feels like — a forgotten staging job, a compromised sidecar in an unrelated app, a contractor&#39;s laptop still sitting on the VPN months after the contract ended can all route a packet to that port just as easily as the real caller can. Service B has no way to tell any of them apart, because it never asked who was calling. It only checked that the request arrived.</p>\n<p>mTLS closes exactly that gap. It turns &quot;reachable&quot; into &quot;who are you, actually,&quot; and enforces the answer on every single connection.</p>\n<h2>What actually happens on the wire</h2>\n<p>Put Service A and Service B on either end of the handshake: A presents its certificate, B verifies it against a trusted CA and confirms A&#39;s identity, and only then does A&#39;s request get processed.</p>\n<figure>\n  <img src=\"/images/mtls-handshake-diagram.svg\" alt=\"Sequence diagram of an SSL/TLS handshake with two-way certificate authentication between client and server\" />\n  <figcaption>A full mutual-authentication handshake: both sides generate and exchange certificates, and both must verify the other before the connection is trusted. Diagram by <a href=\"https://commons.wikimedia.org/wiki/File:SSL_handshake_with_two_way_authentication_with_certificates.svg\" target=\"_blank\" rel=\"noopener noreferrer\">Essich</a>, <a href=\"https://creativecommons.org/licenses/by/3.0\" target=\"_blank\" rel=\"noopener noreferrer\">CC BY 3.0</a>, via Wikimedia Commons.</figcaption>\n</figure><p>That&#39;s the whole pitch for running mTLS between services: you stop trusting the network and start trusting an identity you actually issued and control. It&#39;s also worth saying plainly what mTLS <em>doesn&#39;t</em> do — it tells Service B who&#39;s calling. It says nothing about what that caller is allowed to do once it&#39;s in the door. That&#39;s a separate job, handled by policy, not by the handshake.</p>\n<h2>When it&#39;s worth it, and when it&#39;s just cost</h2>\n<p>mTLS earns its keep once a system has enough moving parts that &quot;it&#39;s on the internal network&quot; stops meaning anything — dozens of services, teams that don&#39;t all know each other&#39;s traffic patterns, or an actual compliance requirement to prove who talked to whom. At that scale, an explicit identity per service is worth more than an implicit one per subnet.</p>\n<p>It&#39;s premature somewhere else: a handful of services, low blast radius, and — this is the part that actually matters — no automated way to issue, rotate, and revoke certificates yet. Adopting mTLS without that automation doesn&#39;t buy you security so much as it buys you a very specific, self-inflicted outage, on a schedule you don&#39;t control. If most of your risky traffic is actually external — partners, enterprise customers hitting an API — a gateway that terminates mTLS at the edge is usually a better fit than trying to hand every third party a client certificate.</p>\n<h2>Where it actually runs</h2>\n<p>Three places, roughly in order of how much application code has to change:</p>\n<ul>\n<li><strong>At a gateway</strong>, between external clients and your edge — good for a smaller number of well-known callers, like enterprise partners.</li>\n<li><strong>Service to service, directly</strong>, where each service terminates its own TLS and checks the caller&#39;s certificate itself — simple to reason about, but every service now owns crypto code and cert config.</li>\n<li><strong>In a service mesh</strong>, where a sidecar proxy next to each service handles the handshake and the policy, and the application mostly doesn&#39;t know mTLS is happening at all.</li>\n</ul>\n<p>For anything beyond a handful of services, the mesh model tends to win on operational grounds, precisely because it centralizes the one thing you really don&#39;t want copy-pasted forty times: certificate handling.</p>\n<h2>The performance cost</h2>\n<p>mTLS costs more per connection than plain TLS, because there&#39;s an extra certificate to check on every handshake. Verifying it burns CPU, and the handshake itself takes longer end to end. The place this actually bites is short-lived connections — a service that opens a fresh TCP connection for every single request pays the full handshake cost every single time, and p95 latency creeps up in a way that has nothing to do with your application logic.</p>\n<p>The fix is almost entirely about not doing handshakes more often than you have to: keep connections alive with keepalive and pooling, use session resumption if your TLS stack supports it, and stop treating &quot;one connection per request&quot; as free. None of this is exotic — it&#39;s the same advice you&#39;d give for plain TLS, just with a slightly less forgiving penalty for ignoring it.</p>\n<h2>Certificate lifecycle: the part that decides the outcome</h2>\n<p>Everything above is the mechanism. This is the operational half, and it&#39;s the one that actually determines whether mTLS works well or badly in practice: certificate lifecycle. Who issues certificates and what identity goes in them. How a workload gets its certificate in the first place. How rotation happens before expiry, without a rollout that kills connections in bulk. How you revoke fast when a key&#39;s been compromised. How you roll the trust bundle itself forward when a CA changes, without one side updating before the other and breaking every handshake between them.</p>\n<p>It&#39;s worth taking seriously because the same fail-closed design that makes mTLS work is not limited to tidy internal services — it&#39;s just as fail-closed on hardware that was never meant to be a cautionary tale. On December 6, 2018, a software certificate quietly expired inside Ericsson&#39;s SGSN-MME equipment, the gear that manages mobile data sessions for carriers worldwide. The equipment did exactly what it was built to do the moment it could no longer verify its own certificate: it stopped. <a href=\"https://techcrunch.com/2018/12/07/heres-what-caused-yesterdays-o2-and-softbank-outages/\">Roughly 32 million O2 customers in the UK and 40 million SoftBank customers in Japan</a> lost mobile data that day, across 11 countries in total — not from an attack or a capacity problem, but from one certificate nobody rotated in time.</p>\n<p>That wasn&#39;t a certificate bug. It was a lifecycle bug wearing a certificate&#39;s clothes. A service mesh with automatic sidecar-issued short-lived certificates — the model Istio ships with — exists specifically so that no single human has to remember an expiry date. mTLS at any real scale is a bet that you&#39;ll build that automation before you need it, not after.</p>\n<h2>What actually breaks, and how to find it fast</h2>\n<p>The failure modes repeat: an expired certificate, clock drift between nodes making a valid certificate look invalid, a trust bundle that&#39;s out of sync across clusters, a certificate&#39;s identity not matching what policy expects, or client and server simply not sharing a cipher suite. When something breaks, the fastest triage isn&#39;t clever — check whether it&#39;s failing during the handshake or after, check whether it&#39;s clustered by node or region or one specific service, and check expiry and clock sync first, because that&#39;s the boring answer most of the time.</p>\n<p>Seeing that quickly requires actually watching for it: handshake success and error rates per upstream, handshake latency (which tells you if it&#39;s a network problem or a certificate problem), days-to-expiry on every identity that matters, and logs that say <em>why</em> a handshake failed, not just that it did. The goal, when something breaks at 2am, is answering three questions fast — where, who, why — instead of staring at a dashboard that says everything is up while nothing is actually working.</p>\n<h2>The tradeoff, plainly</h2>\n<p>mTLS replaces an implicit trust (this request reached me, so it&#39;s probably fine) with an explicit one (this request proved who it is, so I&#39;ll process it). That&#39;s a real security improvement, and it&#39;s why the pattern keeps showing up in zero-trust and service-mesh architectures. It also means you&#39;re committing to run a certificate authority for your own infrastructure indefinitely — issuance, distribution, rotation, revocation, and trust bundle updates, all handled correctly, forever.</p>\n<p>That second part isn&#39;t optional overhead you can skip. Fail-closed authentication is the entire security benefit of mTLS; the tradeoff is that the same mechanism fails the same way when the certificate behind it is wrong, whether that&#39;s one service or, as Ericsson found out, a continent&#39;s worth of phones. Adopt mTLS once the lifecycle automation is solid enough to trust with that job — not before.</p>\n",
      "date_published": "2026-07-28T00:00:00.000Z",
      "authors": [
        {
          "name": "The Duke Post"
        }
      ],
      "tags": [
        "security",
        "mtls",
        "zero-trust",
        "service-mesh",
        "sre",
        "infrastructure"
      ],
      "image": "https://thedukepost.com/_astro/mtls-cell-tower-cover.DMl10H4L_2pvsVl.webp"
    },
    {
      "id": "https://thedukepost.com/articles/pii-is-a-pattern-not-a-field",
      "url": "https://thedukepost.com/articles/pii-is-a-pattern-not-a-field",
      "title": "The Data Had No Name on It. It Still Mapped a Secret Military Base.",
      "summary": "A 2018 fitness-app heatmap outed military bases without a single name field attached — where PII actually hides in your stack, and which of redaction, masking, tokenization, or encryption actually stops it.",
      "content_html": "<p>In November 2017, Strava — the fitness app millions of runners and cyclists use to track their routes — published something called the Global Heatmap. It took every public activity ever recorded on the app and laid them all on one glowing map of the planet: a billion activities, three trillion GPS points, ten terabytes of data. The more people moved through a place, the brighter it glowed. Marketing loved it. It looked like the whole planet was breathing.</p>\n<p>Two months later, Nathan Ruser — a 20-year-old student in Australia — was scrolling over Syria on the map, presumably procrastinating on an assignment like every other 20-year-old on the internet. Most of the desert was dark, since nobody there logs a run on Strava. But a few spots glowed in the unmistakable shape of a running loop, sitting in the middle of nowhere. The same shape showed up around U.S. bases in Afghanistan, around Australia&#39;s secret Pine Gap facility, and over a Chinese military outpost on Woody Island. Soldiers out for a run, wearing a watch that talked to satellites, had traced the outline of their own base — for free, for the whole world, in HD.</p>\n<p>None of that data had a name on it. Strava&#39;s heatmap was anonymized and aggregated — the exact status most privacy programs treat as &quot;job done.&quot; Somewhere, someone in a windowless office had a genuinely terrible Monday.</p>\n<h2>PII isn&#39;t a field. It&#39;s a pattern.</h2>\n<p>Most PII programs work off a list: email, phone, full name, home address, national ID. Find those fields, redact or restrict them, ship it. It&#39;s the compliance equivalent of locking the front door and leaving every window open — technically secure, if nobody looks sideways. That list matters, but it hides an assumption, and that assumption is exactly what got Strava in trouble: identity doesn&#39;t live in one field. It lives in what several fields add up to.</p>\n<p>A GPS ping isn&#39;t PII. A timestamp isn&#39;t PII. A device ID with no name behind it isn&#39;t PII. Strava&#39;s heatmap had none of the fields on anyone&#39;s checklist. What it had was the same anonymous ping, at the same hour, every day, in a spot where the only explanation was &quot;someone stationed at this base.&quot; Privacy researchers have a word for this: a quasi-identifier — harmless by itself, but enough to identify someone once combined with a couple more. It&#39;s also why no single list of PII fields works for every system. The same <code>city</code> or <code>device_id</code> column is just noise in a dataset of a million users, and a full identification in a dataset of a hundred — it depends on what else sits next to it, a caveat that never quite makes it onto the compliance slide.</p>\n<figure>\n  <img src=\"/images/pii-fitness-tracker.jpg\" alt=\"Close-up of a person checking a rugged GPS sports watch mid-workout, showing workout duration, distance, and heart rate.\" loading=\"lazy\" />\n  <figcaption>Distance, heart rate, a timestamp — the kind of data any fitness watch logs by default, none of it a name. Aggregated at scale, that was enough to trace a military base's perimeter. Photo: streetsh / <a href=\"https://unsplash.com/photos/_UZVVThG_u0\" target=\"_blank\" rel=\"noopener noreferrer\">Unsplash</a>.</figcaption>\n</figure><h2>Where PII actually hides — and why the combination is the real risk</h2>\n<p>Picture a smaller version of the same mistake. A support engineer is debugging a failed checkout, pulls up the raw request log, and right next to the stack trace sits a customer&#39;s email, phone number, and shipping address. Nobody chose to log those on purpose — somebody just set <code>log.level = debug</code> back in 2019, and nobody&#39;s been brave enough to touch it since. It&#39;s still PII, still sitting there in plain text, and just as exposed if that log line ends up in a third-party error tracker or in front of a contractor working an unrelated ticket.</p>\n<p>PII collects in the same handful of places, over and over:</p>\n<ul>\n<li><strong>Application, access, and audit logs</strong> — anything captured from a raw request or response body.</li>\n<li><strong>Distributed traces</strong> — span attributes like <code>user_id</code>, client IP, and session tokens, sometimes whole headers or payloads if capture isn&#39;t scoped.</li>\n<li><strong>The primary database, object storage, and the data warehouse</strong> they feed.</li>\n<li><strong>Message queues and event streams</strong>, which send the same data out to every service that&#39;s listening.</li>\n<li><strong>Support tooling</strong> — tickets, spreadsheet exports, one-off data dumps built to answer a single question and then forgotten.</li>\n</ul>\n<p>Logs and tracing are the riskiest of the five. The moment data is captured, it gets copied — to a log aggregator, a monitoring tool, a backup, a data lake — and each copy has its own list of who can see it, a list nobody checked against the others. The same quasi-identifier trap shows up here too. A trace span has no name field, just a <code>user_id</code>, an IP address, and a timestamp — none of which trips a rule built to catch &quot;email.&quot; Joined across enough spans, that&#39;s enough to reconstruct one person&#39;s routine — just like the heatmap did for an army base, except this time it&#39;s Dave from accounting, and somehow that isn&#39;t any more comforting.</p>\n<figure>\n  <img src=\"/images/pii-combination-diagram.svg\" alt=\"Diagram showing three individually harmless fields — a GPS ping, a timestamp, and a device ID — converging into a combined pattern labeled 'same route, every day, same empty desert = one identifiable person'.\" loading=\"lazy\" />\n  <figcaption>None of the three fields on the left would trip a redaction rule written to catch names, emails, or phone numbers. Joined, they're a fingerprint.</figcaption>\n</figure><h2>Four tools, four different jobs</h2>\n<p>Once you know where PII sits, there are four different tools for dealing with it, and people use the names interchangeably in meetings — which is how you end up with a Jira ticket titled &quot;encrypt the PII&quot; that doesn&#39;t actually fix the thing it&#39;s supposed to fix:</p>\n<ul>\n<li><strong>Redaction</strong> removes the field entirely before it&#39;s written — the log line never contains the phone number at all.</li>\n<li><strong>Masking</strong> keeps a short, non-reversible piece of the data for debugging — say, the last three digits of a phone number, enough to confirm &quot;this is the right customer&quot; without a support agent reading the whole thing out loud over the phone.</li>\n<li><strong>Tokenization</strong> replaces the value with a token. Only a separate, locked-down system can map that token back to the real value — useful when you need to join two datasets on a customer without either one holding the raw PII.</li>\n<li><strong>Encryption</strong> protects data sitting on disk or moving over the network, but it doesn&#39;t replace the other three inside a logging pipeline — anyone with normal log access still sees the data in plain text once it&#39;s decrypted for display. Encryption is the lock on the safe. It says nothing about who already has a key.</li>\n</ul>\n<p>Logs and tracing should default to redaction or masking, because that data exists to be read by a person in real time. Databases can lean more on encryption and access control, because reads there go through structured, permissioned queries instead of a person scrolling a raw feed at 2am, squinting.</p>\n<h2>PII and secrets aren&#39;t the same risk</h2>\n<p>PII often gets lumped in with secrets — API keys, passwords, tokens — but they need different defenses. A leaked API key hands someone a capability they can use right away; the fix is to rotate it, quickly. A leaked PII record isn&#39;t a capability, it&#39;s an exposure; the fix is collecting less of it and controlling who can see it, because you can&#39;t rotate someone&#39;s home address no matter how good your incident runbook is. Strava&#39;s leak makes the difference clear. Nothing about it was a credential leak — nobody logged in as anyone. The damage was that a pattern of someone&#39;s daily life became visible to people who were never meant to see it.</p>\n<h2>Why it actually leaks</h2>\n<p>The same causes show up in nearly every PII incident. Someone logs the raw request body &quot;just for now.&quot; Debug logging stays on in production long after the reason for turning it on is forgotten. Tracing is set to capture everything instead of an approved list of fields, because scoping it felt like a problem for later. Exports go out unmasked because the pipeline was built before the policy was. Logs get kept forever because a retention policy is the kind of thing that lives on next quarter&#39;s roadmap and never quite makes it. Different failures. Same shape: a safe default that quietly went unsafe, or a control that only worked if someone remembered to use it — and someone, eventually, doesn&#39;t.</p>\n<figure>\n  <img src=\"/images/pii-dashboard-data.jpg\" alt=\"Close-up of an analytics dashboard on a monitor, showing a users-in-last-30-minutes counter and a top-countries breakdown with user counts by country.\" loading=\"lazy\" />\n  <figcaption>A \"top countries\" panel like this looks like harmless aggregate telemetry — right up until it's small enough, or specific enough in combination with other panels, to point at one person's location and habits instead of a crowd's.</figcaption>\n</figure><p>The fixes that actually hold up are the ones that don&#39;t depend on anyone&#39;s memory:</p>\n<ul>\n<li><strong>Classify by what can be joined, not just by field name</strong> — a device ID next to a rough location and a timestamp is a bigger risk than any one of those alone, so treat the combination, not the single column, as the thing you classify.</li>\n<li><strong>Redact or mask when the data is written</strong>, inside the collector — not later, when someone&#39;s looking at a dashboard. By the time an engineer decides a trace &quot;looks fine,&quot; it&#39;s already been written and copied elsewhere.</li>\n<li><strong>Make the safe behavior the default</strong>, not something a developer has to remember to turn on for each new service. A setting nobody turns on by default only protects the people who already understood the risk in the first place.</li>\n</ul>\n<h2>The part Strava never fixed</h2>\n<p>After the story broke, Strava&#39;s actual fix was to make the opt-out easier to find — it moved the setting onto the first page of privacy settings within weeks, and the problem was declared solved. What it didn&#39;t do was change the default. The heatmap <a href=\"https://www.engadget.com/2018-03-01-strava-simplified-opt-out-heat-map.html\">stayed opt-out, not opt-in</a>. Years later, <a href=\"https://privacy-datahub.csc.ncsu.edu/publication/childs-conpro-2023\">a 2023 study out of NC State</a> found researchers could still identify individual users from the public heatmap, using close to the same trick Nathan Ruser spotted just by scrolling, back in 2018.</p>\n<p>None of this is really about a fitness app. A fix that depends on someone remembering to opt out, or remembering to add one more field to a redaction list, is a fix that has already failed once. The leak that actually gets you is never the field you knew to check for. It&#39;s the one that looked like nothing — right up until it lined up with two other fields that also looked like nothing.</p>\n",
      "date_published": "2026-07-27T00:00:00.000Z",
      "authors": [
        {
          "name": "The Duke Post"
        }
      ],
      "tags": [
        "security",
        "pii",
        "privacy",
        "observability",
        "devops",
        "data-classification"
      ],
      "image": "https://thedukepost.com/_astro/pii-heatmap-lights-cover.Mj_cKjn2_owHd6.webp"
    },
    {
      "id": "https://thedukepost.com/articles/ddos-in-production",
      "url": "https://thedukepost.com/articles/ddos-in-production",
      "title": "The Front Door Won't Close: What a DDoS Attack Actually Does to Production",
      "summary": "DDoS isn't about traffic volume — it's about bottlenecks, capacity, and how well you can tell attackers apart from real users at 3am.",
      "content_html": "<p>It&#39;s 3:14am. The pager goes off with a p95 latency alert on <code>/search</code>. Thirty seconds later, a second alert: 5xx rate above threshold. CPU on the search pods is pinned at 100%. The queue in front of them is growing, not shrinking.</p>\n<p>Every health check is green. The database is fine. The deploy from six hours ago hasn&#39;t changed. Nothing is <em>broken</em> — and yet real users are getting timeouts, and the on-call engineer is staring at a dashboard that says the system is both up and unusable at the same time.</p>\n<p>This is what a DDoS attack looks like from the inside. Not a dramatic outage with red everywhere — a slow, specific squeeze on exactly the resource you didn&#39;t protect.</p>\n<h2>It&#39;s not about how much traffic. It&#39;s about where it breaks first</h2>\n<p>The instinct is to think of DDoS as &quot;too much traffic.&quot; That&#39;s technically true and almost useless as a mental model, because it tells you nothing about what to actually do.</p>\n<p>The more useful framing: <strong>a DDoS attack finds your cheapest bottleneck and pushes on it until legitimate users can&#39;t get through.</strong> Sometimes that bottleneck is raw bandwidth. More often, in modern infrastructure, it&#39;s something narrower and much less obvious — a connection table, an autoscaling policy, a single endpoint that happens to run an expensive database query.</p>\n<p>That distinction matters because it means DDoS resilience isn&#39;t one control, it&#39;s a property of your whole system: where you put protection, how you isolate expensive paths from cheap ones, and whether you can tell &quot;attack&quot; from &quot;launch day traffic spike&quot; before your on-call team burns an hour finding out the hard way.</p>\n<h2>DoS and DDoS aren&#39;t the same problem to defend against</h2>\n<p>DoS and DDoS share a goal — deny service to legitimate traffic — but they don&#39;t share a defense.</p>\n<p>A DoS attack comes from one source, or a small handful. You can often block it with a firewall rule and move on with your night. A DDoS attack is deliberately distributed across thousands of sources — botnets, compromised IoT devices, open proxies — specifically so that IP blocking doesn&#39;t work. Block one range and traffic keeps arriving from ten thousand others.</p>\n<p>That&#39;s the whole reason DDoS is harder: it forces you to defend at a layer above &quot;block the bad IP,&quot; which usually means edge infrastructure you don&#39;t fully control yourself — a CDN, an Anycast network, a scrubbing provider — working together with defenses you <em>do</em> control inside your own stack.</p>\n<figure>\n  <img src=\"/images/ddos-botnet-diagram.png\" alt=\"Diagram showing an attacker issuing commands to a controller, which directs a large network of compromised 'zombie' machines to flood a single victim server with traffic.\" loading=\"lazy\" />\n  <figcaption>Every arrow into the victim comes from a different machine — none of them the attacker's own. Diagram: Nasanbuyn / <a href=\"https://commons.wikimedia.org/wiki/File:Ddos-attack-ex.png\" target=\"_blank\" rel=\"noopener noreferrer\">Wikimedia Commons</a>, <a href=\"https://creativecommons.org/licenses/by-sa/4.0/\" target=\"_blank\" rel=\"noopener noreferrer\">CC BY-SA 4.0</a>.</figcaption>\n</figure><h2>Three attacks wearing the same name</h2>\n<p>&quot;DDoS&quot; gets used as a catch-all, but the three common flavors behave completely differently, and only one of them looks like an obvious attack.</p>\n<p><strong>Volumetric</strong> is the loud one — raw bandwidth, saturating the pipe into your infrastructure before packets even reach your load balancer. Easy to see on a graph, hard to fight without upstream help, because by the time it hits your servers, the damage is already done at the network link.</p>\n<p>The clearest real-world case is still <a href=\"https://github.blog/news-insights/company-news/ddos-incident-report/\">GitHub&#39;s February 2018 incident</a>: a memcached-reflection attack — abusing publicly exposed memcached servers to amplify traffic roughly 51,000x — peaked at 1.35 Tbps and 126.9 million packets per second, the largest publicly recorded volumetric attack at the time. GitHub.com was fully unavailable for about five minutes and intermittently degraded for a few more, until its network engineers withdrew their normal transit routes and re-announced their address space exclusively through Akamai&#39;s scrubbing network. Full recovery: roughly ten minutes after the attack started. Nothing about GitHub&#39;s own application code changed in that window — the fix was entirely &quot;reroute through infrastructure built to absorb this,&quot; which is exactly why volumetric attacks are the one category edge protection has to handle before it ever reaches you.</p>\n<p><strong>Protocol</strong> attacks target state, not bandwidth — a classic SYN flood opens huge numbers of half-finished TCP connections to exhaust a connection table or a load balancer&#39;s backlog. The traffic volume can look almost modest. What&#39;s exhausted is capacity to <em>track</em> connections, not capacity to serve them.</p>\n<p><strong>Application-layer</strong> is the one that should worry you most, because it&#39;s the one that looks like your own users. An HTTP flood against a normal-looking endpoint, credential-stuffing traffic dressed up as login attempts, a scraper hammering your search bar — all indistinguishable from real demand until you look at what it&#39;s actually doing to your backend. A <code>/search</code> endpoint with no cache and no rate limit doesn&#39;t need a botnet to fall over; a few thousand requests a second that all miss cache and hit the database will do it.</p>\n<figure>\n  <img src=\"/images/ddos-osi-layers-diagram.svg\" alt=\"The seven-layer OSI model, from Physical at the bottom through Data Link, Network, Transport, Session, Presentation, up to Application at the top.\" loading=\"lazy\" />\n  <figcaption>Volumetric attacks hit the lower layers (Physical/Network) before packets even reach your load balancer. Protocol attacks target Transport-layer state (SYN backlogs, connection tables). Application-layer attacks skip straight to the top — layer 7 — which is exactly why they look like ordinary traffic. Diagram: Offnfopt / <a href=\"https://commons.wikimedia.org/wiki/File:OSI_Model_v1.svg\" target=\"_blank\" rel=\"noopener noreferrer\">Wikimedia Commons</a>, public domain.</figcaption>\n</figure><p>That third category is why &quot;just add more servers&quot; stopped being an answer years ago. You can autoscale in front of an application-layer attack all night — and the attacker gets exactly what they wanted anyway: your cloud bill climbing while real users still time out, because the bottleneck was never compute in the first place.</p>\n<h2>Where it actually lands</h2>\n<p>In production, the same handful of places take the hit, over and over:</p>\n<ul>\n<li><strong>Bandwidth and egress</strong>, especially for anything public-facing without a CDN in front of it.</li>\n<li><strong>Load balancer capacity and connection limits</strong> — the layer everyone assumes is infinite until it isn&#39;t.</li>\n<li><strong>Connection state</strong>: SYN backlogs, connection tracking tables, pool limits.</li>\n<li><strong>Application hotspots</strong> — the one expensive endpoint, the one unindexed query, the one dependency with a slow timeout.</li>\n<li><strong>Shared dependencies</strong> — a DNS resolver, a NAT gateway, an upstream API — that quietly become a single point of failure for services that thought they were independent of each other.</li>\n</ul>\n<p>That last one is the sneaky part. Two teams can each believe their service is resilient, right up until an attack on Service A saturates a NAT gateway that Service B also depends on, and now B is down too, for reasons its own on-call has no visibility into.</p>\n<h2>Reading the signs before they read you</h2>\n<p>The pattern is usually recognizable, if you&#39;re looking at the right slice of data: RPS spiking on a narrow set of endpoints rather than uniformly across the site, p95 latency climbing with no corresponding deploy, 5xx and timeout rates rising, CPU and packet loss climbing together, and load balancer connection counts moving in a way that doesn&#39;t match your normal traffic shape.</p>\n<figure>\n  <img src=\"/images/ddos-monitoring-dashboard.jpg\" alt=\"Close-up of a monitoring dashboard showing multiple metric tiles with line graphs, including click-through rate and quality score panels.\" loading=\"lazy\" />\n  <figcaption>A single spiking tile means nothing on its own — it's the breakdown by endpoint and status code, side by side, that turns a metrics wall into a diagnosis. Photo: Stephen Dawson / <a href=\"https://unsplash.com/photos/turned-on-monitoring-screen-qwtCeJ5cLYs\" target=\"_blank\" rel=\"noopener noreferrer\">Unsplash</a>.</figcaption>\n</figure><p>The catch is that none of this is visible if your dashboard only shows total RPS. A flood against one endpoint can hide comfortably inside an aggregate number that still looks unremarkable. The fix is cheap: <strong>break down traffic by endpoint and status code by default</strong>, and by region or ASN when you can, so an anomaly on one path doesn&#39;t get averaged away into &quot;traffic&#39;s a little high today.&quot;</p>\n<h2>Building for the attack you haven&#39;t had yet</h2>\n<p>None of this requires exotic tooling — mostly it requires deciding, before an incident, which of these you actually have:</p>\n<ul>\n<li>Protection as close to the edge as possible — a CDN or Anycast network in front of anything public, so volumetric traffic never reaches infrastructure you pay to scale.</li>\n<li>Rate limits scoped to client, token, and endpoint — not a single global limit that a determined attacker on one path can spend without anyone else noticing.</li>\n<li>Separation between sensitive and ordinary traffic — admin paths and internal APIs living somewhere an HTTP flood against the public site can&#39;t touch.</li>\n<li>Observability that defaults to endpoint- and status-code-level breakdowns, so triage doesn&#39;t start with &quot;let me go figure out what normal even looks like.&quot;</li>\n<li>A pre-built, controlled degradation mode — serve a lighter response, extend a cache TTL, disable a non-critical feature — so the core user path survives even if a corner of the product doesn&#39;t.</li>\n</ul>\n<h2>What actually turns an attack into an outage</h2>\n<p>Attacks happen to everyone with public infrastructure, eventually. Whether it becomes a five-minute blip or a multi-hour incident usually comes down to a short list of gaps: no CDN or edge layer in front of public traffic, no per-endpoint rate limiting on the requests that are actually expensive, no per-tenant quota so one noisy customer can&#39;t starve everyone else, load balancers with connection limits nobody load-tested, and — the quiet killer — no runbook, so the first thirty minutes of a real attack get spent re-discovering things a calmer version of the team already knew.</p>\n<h2>The endpoint that started it</h2>\n<p>Back to <code>/search</code> at 3:14am. In the version of this story that ends badly, there&#39;s no cache, no rate limit, and the runbook is a Slack thread from the last incident that nobody bookmarked. CPU stays pinned, the queue keeps growing, and the fix is improvised live, in production, while users churn.</p>\n<p>In the version that ends in twenty minutes: the dashboard already shows the spike is concentrated on one endpoint, a rate limit throttles the offending pattern without touching everyone else, a cache in front of the expensive query buys immediate breathing room, and the runbook says exactly who to loop in if it doesn&#39;t resolve on its own.</p>\n<p>Same attack. The difference was entirely decided before it started.</p>\n",
      "date_published": "2026-07-26T00:00:00.000Z",
      "authors": [
        {
          "name": "The Duke Post"
        }
      ],
      "tags": [
        "security",
        "ddos",
        "sre",
        "incident-response",
        "infrastructure"
      ],
      "image": "https://thedukepost.com/_astro/ddos-network-cables-cover.RtbjaqAx_Z1Musu9.webp"
    },
    {
      "id": "https://thedukepost.com/articles/welcome",
      "url": "https://thedukepost.com/articles/welcome",
      "title": "Welcome to The Duke Post",
      "summary": "What this site is, and how it works.",
      "content_html": "<p>This is the first post on The Duke Post — a tech publication built on a simple idea: original writing where it matters, and an honest, fast-moving feed of what everyone else is reporting, clearly labeled as such.</p>\n<h2>How content works here</h2>\n<p>There are two kinds of posts on this site:</p>\n<ol>\n<li><strong>Articles</strong> — written here, by us. Opinion, analysis, explainers.</li>\n<li><strong>The Wire</strong> — a curated feed of headlines from other publishers, refreshed regularly. We link out; we don&#39;t republish.</li>\n</ol>\n<p>Replace this post with your own first article by editing or deleting this file in <code>src/content/articles/</code>.</p>\n",
      "date_published": "2026-06-20T00:00:00.000Z",
      "authors": [
        {
          "name": "The Duke Post"
        }
      ],
      "tags": [
        "general",
        "meta",
        "about"
      ],
      "image": "https://thedukepost.com/_astro/welcome-cover.DF9iVkUv_2eGRNw.webp"
    },
    {
      "id": "https://thedukepost.com/articles/how-the-wire-works",
      "url": "https://thedukepost.com/articles/how-the-wire-works",
      "title": "How the Wire feed stays current",
      "summary": "A quick note on the RSS sync mechanism behind The Duke Post.",
      "content_html": "<p>The Wire section pulls from a small set of RSS feeds defined in <code>scripts/sync-feeds.mjs</code>. Running <code>npm run sync-feeds</code> fetches the latest items, trims them to a short excerpt, and writes them to <code>src/data/wire.json</code>, which the homepage and Wire page read from at build time.</p>\n<p>To keep it current automatically, you can wire the sync script into a scheduled GitHub Action that runs on a cron schedule and commits the refreshed JSON, or trigger a redeploy on your host of choice.</p>\n",
      "date_published": "2026-06-19T00:00:00.000Z",
      "authors": [
        {
          "name": "The Duke Post"
        }
      ],
      "tags": [
        "general",
        "meta",
        "wire",
        "rss"
      ],
      "image": "https://thedukepost.com/_astro/wire-sync-cover.CBkDzkyA_1fkR7W.svg"
    }
  ]
}