This is an illustrative scenario built from patterns we see repeatedly, not a named customer. The mechanisms — L7 request-shape learning, DNS/egress baselining, and cross-layer correlation — are exactly how Nemesis Shield behaves.
The situation
A cross-border remittance company runs a payments API and a customer console. They already did the responsible thing: a positive-security Application Shield (Sentinel) on the API, learning every route, method, parameter shape and auth state, then enforcing that only the shapes the app actually serves get through. It works. Their console's own traffic looks like this to the shield:
GET /app/applications/{uuid} auth=true ← IDs normalized, so one behavior, not thousands
GET /transfers/{int} auth=true
GET /.env auth=false ← scanner probe: off-baseline, flagged
POST / auth=false ← method anomaly: off-baseline, flaggedSo they can see, and block, anyone attacking the app: BOLA attempts on /transfers/{int}, scanners hitting /.env and /wp-login.php, injection on a route that never took a body before. That is real, and it is L7.
Their fear was the attack that doesn't look like a request at all.
The attack
An attacker didn't bother fighting the front door. They poisoned a build dependency — a small npm package pulled into the API's Docker image. On the next deploy, that package woke up inside the running service and began doing two things:
- Beaconing to a command-and-control domain every few minutes, over plain HTTPS, to blend in.
- Exfiltrating batches of transfer records by resolving a rotating set of subdomains (
a3f9.exfil-node.io,b7c1.exfil-node.io, …) — classic DNS-adjacent staging.
Here is the uncomfortable part: the Application Shield saw none of this, and it was right not to. The malicious code wasn't serving an off-baseline inbound request. It was the app, making outbound connections. There is no route, no method, no auth flag to judge. L7 is the wrong altitude for an egress problem.
flowchart TB
subgraph inbound["Inbound (L7) — what the App Shield sees"]
A1["BOLA on /transfers/{int}"] -->|off-baseline| APP["Application Shield<br/>learns route+method+params+auth"]
A2["scanner: /.env, /wp-login.php"] -->|off-baseline| APP
A3["injection on a clean route"] -->|off-baseline| APP
end
subgraph outbound["Outbound (L3/L4 + DNS) — what the App Shield is blind to"]
C1["poisoned dependency<br/>beacons to C2 domain"] --> NET["Network / DNS Shield<br/>learns which domains+IPs<br/>the service normally reaches"]
C2["exfil via rotating<br/>a3f9.exfil-node.io …"] --> NET
end
APP -. "same positive-security engine,<br/>different altitude" .- NET
Where each layer cut its link
- The inbound attacks: Application Shield. The BOLA attempts, the scanners, the injection — all off-baseline request shapes. A shield that learned the API's real surface blocks them at the boundary, unpatched framework or not. (In our own dogfooding this is exactly the class we capture: normalized routes, scanner paths like
/.envrecorded as distinct behaviors, method and auth anomalies.) - The beacon and the exfil: Network / DNS Shield (Edge). The App Shield can't see a service phoning a C2 — but the egress shield can. It had already learned the small, fixed set of domains this service legitimately reaches (its payment processor, its own APIs, a logging endpoint).
exfil-node.ioand the C2 domain were not in that learned envelope. Default-deny on egress: the beacon and the exfil resolve to nothing. The prize never leaves.
Neither layer is redundant. The App Shield is blind to a compromised-from-within egress. The Network Shield is blind to BOLA — GET /transfers/99 is a legitimate host talking to legitimate peers; only the app knows it's the wrong customer's money. Each covers precisely the other's blind spot.
The multiplier: one incident, correlated
The reason to run both under one behavioral backend isn't tidiness — it's that the whole story only exists when the layers are joined:
flowchart LR L7["App Shield:<br/>odd requests from<br/>an internal IP"] --> COR["Correlation<br/>(one backend, one model)"] DNS["DNS Shield:<br/>same service now<br/>resolving exfil-node.io"] --> COR COR --> INC["ONE incident:<br/>compromised service,<br/>active exfiltration"]
Separately, each is a shrug: "a few blocked requests" and "a domain we don't recognize." Joined, they are a single, high-confidence incident — this service is compromised and actively exfiltrating — with the app-layer and network-layer evidence already stitched together. A WAF-only product can't do this. A DNS-filtering product can't do this. The correlation is the moat.
Why the principle is the same, and why that matters
Both layers are the identical idea at a different altitude:
- Application Shield answers: "Is this request to my app one the app is actually supposed to serve?"
- Network / DNS Shield answers: "Is this destination one my infrastructure is actually supposed to reach?"
Both learn your own normal (learn → observe → enforce), both default-deny outside it, both never trust a signature. That sameness is a feature: one mental model, one console, one enforcement lifecycle — applied to inbound requests and outbound connections, then correlated.
The lesson
Attackers move between layers on purpose. They fight the app when the app is the door, and they abuse the network when they're already inside. A defense that only watches one altitude will, on its worst day, block the request and still watch the data leave — and never connect the two. Learn your normal at both altitudes, deny the rest, and join the signals: that is how a blocked request and an unrecognized domain become one caught breach instead of two ignored logs.
