There seems to be a lot of commentary on the CVE-ageddon around “we don’t expose our apps to the internet, we use a VPN/Tunnel, so most of this doesn’t apply to me”. I get it, if your app is not exposed to hostile sources, how can it be scanned or exploited by a stranger on the other side of the world?
The problem is that attackers never go after a single application in isolation; they build a chain (an entry point, a foothold, a way to move, and finally the thing they came for). A chain is only as strong as its weakest link, and the weakest link is whichever one is easiest to reach, and every business has at least one of those (and that could be a human, or these days, and AI agent, not an exposed network service).
The volume is real
The numbers are big enough that I read the shrug as fatigue more than judgment. Microsoft’s September 2026 Patch Tuesday counted 963 CVEs needing customer action, 106 of them critical, with two already exploited when the release shipped. Add the browsers, the Linux distributions, the container base images, the language libraries, the VPN gateways, the hypervisors and container runtimes, and whatever your developers pulled from a public registry last quarter, and the pile gets a lot taller. Heck, this October has been an unmitigated nightmare in regard to zero-day vulnerabilities discovered, and made even worse by a few breaches of coordinated release schedules. Its now impossible ignore the risk.
A chain is only as strong as its weakest link
Attackers don’t pick your most valuable system and hit it head on, they look for whatever is easiest to reach and then work out what it gets them. That first compromise is often worth very little on its own (a developer laptop, a build runner, a forgotten test instance, a marketing site on shared hosting); its only job is to provide a position from which the next step becomes possible. Each link in the chain is frequently rated medium severity by itself, which is why a CVE list triaged one entry at a time understates the risk so badly. A privilege escalation that “requires local access” sits low on the list right up until the day someone has local access.
This is the part of the “we’re not exposed” argument that I think gets missed most. It asks whether an attacker can reach a vulnerability today from the internet, when the question that decides the outcome is whether an attacker who has already reached something else can reach it tomorrow. The answers to those two questions are very different, and only the second one tells you whether a small incident stays small.
Every business exposes something
Every business, every one of them, exposes some services to the internet. The website, the mail gateway, the remote access gateway itself, the customer portal, the API a partner calls, and the SaaS applications that hold your data and that people log into from anywhere. Even a business with genuinely no inbound listeners runs software it didn’t write, pulled from registries it doesn’t control and updated by vendors whose own build systems are targets. The VPN or tunnel that’s supposed to be the answer is itself an internet-facing service with its own code, its own CVEs and its own patch cycle, so when someone says nothing is exposed, what they usually mean is that the one exposed thing is the thing everybody trusts.
How do your contractors connect remotely? Which SaaS tools have a path into the internal network through a connector or an agent? Which pipelines pull dependencies on every commit? Which internal services call out to the internet for an auto-update check, a webhook, a license server or a telemetry endpoint? Every one of those is a door, and several open from the inside, so they sidestep the inbound firewall rule that the “not exposed” argument leans on. An attacker who controls something inside can simply call out to a server of their own and wait for instructions.
The most common entrypoint
In my view software supply chain compromise is the most common way in, because it needs nothing from the victim except that they do their job. A developer installs a dependency, a pipeline pulls an image, an update agent applies an update, and malicious code arrives through the front door of a process your organization already trusts. Nothing was exposed to the internet in that story, and the attacker is inside anyway.
AI agents are the newer link, and they’re arriving fast. An agent that reads email, browses pages, summarizes documents and calls internal tools can be steered by instructions hidden in the content it processes (prompt injection), so the attacker doesn’t have to compromise a human or find a vulnerability; they have to put text where an agent will read it. The agent already has legitimate access to internal systems, which means a successful injection looks like a trusted internal identity doing something slightly odd, and that’s among the hardest behavior to spot. Each agent you wire into internal data and tools is another person-shaped link in the chain, one that works around the clock and never gets suspicious.
Humans are the weakest link in most chains, and agents are quickly joining them. Both sit on the inside by design, so no firewall in front of your applications changes their position.
Lateral movement turns a foothold into a breach
With a position inside, the attacker’s job changes from breaking in to moving around. They look at what the compromised machine can see, scan for internal services, pull credentials out of memory, config files, environment variables and kubeconfig files, reuse tokens, and go after whichever internal service looks softest. Then they do it again from the new spot, and each hop widens the view until they land on the identity or system holding what they came for.
The softest targets are very often the internal-only services, because “it’s behind the VPN” is the reason they were left unpatched. I can hear the conversation already: “that CVE is critical, but the service isn’t exposed, so we’ll pick it up in the next maintenance window, and I’ve asked the team to schedule it for next quarter”. Repeat that decision across a few hundred services and a few thousand CVEs and you’ve built a flat interior full of documented, unpatched weaknesses, which is a very efficient playground for anyone who gets in.
Container platforms add another dimension. A container that runs as root, holds extra Linux capabilities, mounts the host filesystem or mounts the container runtime socket is a short step from controlling its host, and a host running forty other workloads is a short step from the orchestrator’s credentials. One privilege escalation or container escape turns a compromised application into a compromised node, and a compromised node into a compromised cluster, without the attacker needing another vulnerability in anything you wrote.
What actually works
All three controls below start from the assumption that an attacker will get a foothold somewhere, sooner or later, and that the job is to make the foothold worthless. I don’t see a fourth option that works if any of these three is missing.
Patch, often
Move from a patch cycle measured in quarters to one measured in days for anything that matters, and automate it far enough that patching is the default state. Nobody patches everything at once at these volumes, so start with internet-facing systems, identity infrastructure, the VPN and remote access layer, browsers and mail clients, and anything on CISA’s Known Exploited Vulnerabilities catalog, since that list tracks flaws confirmed as exploited in real attacks. Under this model “it’s internal” stops being an acceptable reason to defer, because the chain argument above shows internal services are where lateral movement lands next.
Patching also has to reach the things teams forget: container base images, the libraries baked into them, and the orchestrator and runtime themselves. An image built eight months ago and never rebuilt carries eight months of disclosed vulnerabilities into every cluster that runs it, and a quarterly cycle makes that permanent.
Secure east-west traffic
East-west traffic is the traffic between your internal workloads, and in most environments it’s wide open by default. Flip that default so every workload is denied access to every other workload unless a specific flow is allowed, which turns a flat network into a set of small rooms with locked doors. An attacker who compromises one service then reaches only the handful of services it legitimately talks to, and the sweep across the internal network that makes lateral movement so productive comes back nearly empty. Apply the same logic to egress; a workload with no reason to call the internet shouldn’t be able to, which cuts off the outbound path attackers use to fetch tools and receive instructions.
On Kubernetes this is done with network policies, and they only take effect when the cluster’s network plugin enforces them, so confirming that is the first job. Mutual TLS between services adds identity on top of the network rules. With both in place a breach of one component stays contained, and the weakest link stops being the one that decides the outcome.
Run a platform that blocks escape and privilege escalation
Most container escapes and privilege escalations depend on a workload being allowed to do something it never needed to do: run as root, hold broad capabilities, run privileged, mount host paths, mount the runtime socket or share host namespaces. Run your workloads on a platform that refuses those configurations at deploy time and constrains the runtime behavior of everything that does get through, using non-root users, dropped capabilities, read-only root filesystems, seccomp and mandatory access control profiles, and admission control that rejects non-compliant workloads before they start. On Kubernetes, Pod Security Admission with the restricted profile is the baseline, with policy engines layered on top where you need more.
Enforcement has to sit at a point that can’t be sidestepped. If the engine will still execute the same request when it arrives by another path (the Docker socket is the usual example), the control isn’t an enforcement point whatever the interface says, because the attacker will take the other path. So here’s the question for any container platform you rely on: when a workload asks for privileged mode or a host mount, who decides, and can the answer be bypassed by talking to the runtime directly?
One question to take back to your team
How many of the services on your internal network would survive an attacker who’s already on it? If the honest answer is “I don’t know”, the chain is longer than you think, and the weakest link is probably sitting somewhere you’ve been told is safe.
