I came across a sponsored post from Intercept Brasil asking for donations to reinforce the site’s security. According to the outlet itself, the story involving the leaked audio between Flávio Bolsonaro and banker Daniel Vorcaro went offline shortly after being published, and now they’re asking readers for money so it doesn’t happen again.

I got curious right away about what might have happened. Security is one of those investments that, when not made early, gets charged back double when you least expect it. Security not invested in early has a habit of becoming a company’s biggest budget line item at exactly its most critical moment.

Reading that donation ask, I couldn’t help but imagine what gaps might be behind it:

  • Was there some page or route that was heavier to process, with no request limit, just waiting for any traffic spike to bring the whole system down?
  • Was the origin IP exposed enough for someone to skip the edge protection and go straight to the source?
  • Did some kind of infrastructure hijack, partial or total depending on the level of the exploited vulnerability, play a role in this?

I don’t know, and there’s no way for me to know from the outside. What I do know how to recognize is the outlet’s level of exposure: having to ask for donations publicly already reflects the severity of the problem, something they probably never imagined would become a vulnerability for attackers with the worst intentions.

This post isn’t an investigation into the Intercept case: I have no access to anything that confirms what actually happened on the inside. What I have is 7+ years working with technology in media and communications, enough time to notice that companies in this sector tend to share similar system structures, and that’s where the background to speculate comes from. Everything that follows is my hypothesis, not established fact.


Three ways to go down from traffic overload

“It went down after going viral” doesn’t point to a single cause. At least three different things show that, and each calls for a different fix:

  • Cache stampede (herd effect): a page that didn’t exist an hour ago suddenly gets millions of requests. Since nothing is cached yet, every request hits the backend directly: database, rendering, everything at once. Nobody needs to be attacking anything — a link popular enough produces the same load as an attack. It’s the most innocent of the three scenarios, the kind of thing a prepared company doesn’t even feel.
  • L7 flood: a wave of individually valid requests, real browsers or bots, but deliberately targeting the most expensive endpoints: search, comments, anything that fires a heavy query instead of returning a static file. If someone really wanted to take the site down right after that story, this is the first attack I’d imagine: cheap to set up, hard to tell apart from genuine curiosity at scale.
  • Classic volumetric DDoS: brute force at the network layer to saturate bandwidth or connection tables, with no interest whatsoever in which page is being hit. Just about taking everything down.

If I had access to Intercept’s logs, the practical way to tell the three apart would be to look at where the errors concentrate. Accumulated 5xx on a single URL, with a large number of distinct IPs, looks like attention — organic or targeted — converging on one page. Errors spread across the entire site, with no hot URL, points more toward a network or infrastructure problem. Neither proves intent, but it would tell me which layer to investigate first.


The edge isn’t decoration

One thing I suspect: a good chunk of the confidence a media company places in its own security comes just from being behind a CDN like Cloudflare, Azion, or Akamai. It’s easy to treat that as a checked box — “we’re behind the CDN, we’re protected.” And the edge does do real work: it absorbs raw volumetric traffic before it reaches the servers, applies application firewall and bot detection, enforces global rate limiting across all its points of presence, caches what it can.

But the edge doesn’t know the application behind it. It doesn’t know that /search is expensive and /robots.txt is free. That blind spot is a classic candidate for a gap that should be thought through by the system’s engineers. Without security policies at the next layer — Nginx (or another reverse proxy) in front of the application — the edge becomes the only point of protection, not the first of two. At minimum, this second front needs to have:

  • Per-client rate limiting, so a single origin can’t monopolize a shared resource even if the edge let the traffic through.
  • Simultaneous connection limits per client.
  • Its own cache, independent of the edge, so that a cache miss up front doesn’t turn into a full, cache-less hit on the application.
  • A rule that only accepts traffic coming from the edge. Possibly the most likely gap of all, and a subject I’ll come back to further down.

It costs a few hours of configuration today. It seems completely dispensable until the day it becomes the difference between an annoying traffic spike and a headline about a donation ask.


When cache lies: poisoning and deception

If I had to bet on a gap exploited by attackers in this case, it would be the one that happens most often and is cheapest to exploit: a misconfigured cache. If the problem wasn’t just volume, but some kind of partial hijack of the content served, this is one of the quietest ways for that to happen — no noise, no obvious error log, just a wrong response being delivered to everyone.

Cache poisoning: a header influences the response without being part of the cache key. X-Forwarded-Host, Accept-Language, X-Forwarded-Proto can change what a backend renders (a link, a redirect, a chunk of embedded content) without being included in what defines “the same request.” One forged request, one poisoned response, and if that gets cached, every subsequent visitor gets the same poisoned page without the attack needing to repeat. If something like this had happened at Intercept, it would explain an infrastructure hijack without requiring any server access at all.

Cache deception: the opposite. Tricking the edge into caching something that should never have been cached — typically a logged-in user’s page, misidentified as static content because of a .css or .jpg at the end of the URL. A cache that decides by extension instead of looking at the actual response can end up serving one user’s private page to anyone else.

The mitigation for both is the same: be deliberate about what defines a cache entry. An explicit, narrow cache key, instead of trusting the defaults. Headers normalized before reaching the cache logic. Vary set on purpose, not as a forgotten detail. And never deciding by file extension alone.


The origin IP isn’t a secret

There’s a second gap I wouldn’t rule out, though maybe less as a direct cause and more as a second stage of the attack. None of the edge protection matters if an attacker can simply skip it and talk directly to the server, and getting there doesn’t always depend only on finding an exposed IP: a firewall rule can be circumvented, VPN access can be compromised, or the internal cluster itself might be misconfigured and leak endpoints that should never be visible from outside.

If Intercept’s origin IP were simply exposed by oversight, a direct DDoS on the origin, without even going through Cloudflare, would take the service down all the same, no matter what was configured at the edge. But I find that specific version unlikely: the bare minimum expected of security compliance in an organization of that size is a virtual private network isolating routing to the backend. What I find more plausible is the second stage — someone working around that VPN, or exploiting a cluster misconfiguration to reach internal services through another path.

The fix, either way, is architectural, not a patch: origin firewall accepting only the IP ranges published by the CDN, VPN with strong authentication and no routes broader than necessary, and cluster configuration audits to make sure no internal endpoint is accessible from outside by oversight. On the Nginx side, the real_ip module keeps logs and rate limiting seeing the visitor’s real IP, not the edge’s own address.


Cache as resilience, not just performance

One detail that hasn’t left my head, reading that donation ask: was it maybe just a missing, cheap configuration directive? Cache serves two quite different purposes. Performance: avoiding redoing expensive work for no reason. Resilience: what the cache does for you when the origin is having trouble.

Every cache entry has an expiration deadline, and when that deadline passes without anyone refreshing the content, it becomes what’s called “stale”: expired, but still there, stored. What the server does with an expired entry during that window, instead of simply discarding it, is defined by two configuration directives little known outside infrastructure teams.

The first is stale-while-revalidate, and it addresses the performance side: it serves the expired response immediately, without making the visitor wait, and updates the content in the background. Only the next visitor gets the new version — nobody waits for a live render to see the page load.

The second is stale-if-error, and this one addresses the resilience side — it’s the directive I’d want to know was configured at Intercept. When the origin starts returning errors, it instructs the server to keep serving the last known good cached response, instead of propagating the error to the visitor. A reader with stale-if-error configured sees the older version of the story. A reader without it sees a blank error screen — and it’s exactly that second experience that turns into a screenshot, turns into a complaint, turns into the “the site went down” narrative. Would it have changed the outcome in this specific case? I have no idea. But it’s frustrating to think it might have been something this simple to turn on.

The same logic for handling expired content applies to when a cache entry is actually refreshed. A short, generic TTL is often sold as “freshness,” but it’s also the thing that expires the protection right when a traffic spike needs it most. Event-driven purge — a webhook fired the moment a new article is published — keeps content up to date without pulling out the safety net during the exact traffic pattern a viral story produces.


Setting up the second line of defense

If I were setting up the origin for a news site today, knowing any story could be the one that goes viral, this is roughly how I’d leave the second line of defense configured. Concept without code isn’t worth much.

http {
    # Defines a shared rate-limit "zone", indexed by client IP.
    # 10m of shared memory tracks roughly 160,000 IPs;
    # 10r/s is the sustained rate allowed per IP.
    limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;

    # Defines a shared zone to limit simultaneous connections per IP.
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

    # Trusts the CDN's IP as the proxy hop, and reads the visitor's
    # real IP from the header it forwards; without this, every
    # request in the logs and rate limit appears to come from the CDN itself.
    set_real_ip_from 173.245.48.0/20;   # example: Cloudflare range
    set_real_ip_from 103.21.244.0/22;   # (repeat for each published range)
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;

    # Where cached responses live on disk, how much of that is kept
    # as a key index in memory (keys_zone), and the default TTL
    # for a successful response.
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=main_cache:10m
                     max_size=1g inactive=60m use_temp_path=off;

    server {
        listen 443 ssl;
        server_name example.com;

        # Only accepts traffic that arrived through the CDN; a firewall
        # rule at the origin achieves the same effect at the network
        # layer — this is the equivalent/reinforcement at the application layer.
        # (allow/deny list with the provider's ranges omitted for brevity)

        location / {
            # Rejects clients that exceed the limit instead of queueing them
            # indefinitely; a small burst absorbs normal browser behavior
            # (a page loading several assets at once) without loosening
            # the sustained rate.
            limit_req zone=per_ip burst=20 nodelay;
            limit_conn conn_per_ip 20;

            proxy_pass http://app_backend;
            proxy_cache main_cache;

            # The cache key is explicit and deliberately narrow;
            # it doesn't include headers that shouldn't influence
            # what gets cached (see: cache poisoning).
            proxy_cache_key "$scheme$request_method$host$request_uri";

            # If the backend is down, erroring, or timing out,
            # keep serving the last good cached copy instead of the error;
            # this is the resilience mechanism, not just a speed trick.
            proxy_cache_use_stale error timeout updating
                                  http_500 http_502 http_503 http_504;

            # Refreshes an expired entry in the background, instead of
            # making the visitor who triggers the refresh wait for it.
            proxy_cache_background_update on;
        }
    }
}

None of these directives is exotic — they all ship with standard Nginx. What stands out is that they’re rarely all switched on together: each one in isolation seems optional until the day it stops being one. On that day, the price isn’t paid in configuration hours. It’s paid in a donation ask.


An audit, not a verdict

I still don’t know why Intercept’s site went down, and this post isn’t meant to find out. What I’m left with is confirmation of something I already suspected: postponed security isn’t saved security. It’s security financed later, with interest, at the worst possible time. Sometimes literally as a donation ask stamped on the readers’ own feed.

There’s a short list of questions worth asking about any site behind an edge today, including mine:

  • If our most-shared page got a huge traffic spike with no attacker involved, would the origin survive, or does all our protection assume the edge secures everything?
  • Could someone find our origin’s real IP through DNS history, a forgotten subdomain, or a certificate log? And if they found it, would our firewall notice?
  • Does our cache key include something that shouldn’t be able to change what’s served to the next visitor?
  • If our backend started erroring right now, would visitors see an outdated but working page, or a blank error screen?

None of these questions requires knowing what actually happened to someone else’s site. They just require doing this math before the bill arrives, not after.