latest newsArgo CDKubernetesGitOpsRCEnetwork policyRedis cache poisoningkustomizesupply chain security

The Argo CD Flaw That Wasn't Patched: What Happens When Your GitOps Pipeline Becomes the Attack Vector

Geert WarmenbolGeert WarmenbolJuly 2, 2026
Share:
The Argo CD Flaw That Wasn't Patched: What Happens When Your GitOps Pipeline Becomes the Attack Vector

The alert came in at 3:14 AM. A Kubernetes cluster running a financial services application had just deployed a new workload. The team hadn't pushed any changes. No one had touched the Git repository. The deployment just happened. The pod that appeared was running a cryptocurrency miner. The team scrambled to kill it. It came back. They killed it again. It came back again. The sync loop was still running. Argo CD, the GitOps tool they trusted to keep their cluster in a known state, was now the mechanism keeping the attacker's workload alive. The team had no idea how the attacker got in. They had network policies. They had RBAC. They had everything the compliance framework told them to have. But they had not checked one thing: whether the network policies were actually enforced. The Helm chart that installed Argo CD had left them off by default. The team never noticed. The attacker did.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume mount. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

CASE RECONSTRUCTION

The attacker did not need a zero-day in the traditional sense. They needed a path to the repo-server's gRPC port, port 8081. In a cluster where network policies were not enforced, that path existed from any pod. The attacker compromised a low-value container running a vulnerable web application. From there, they scanned the cluster's internal IP range. They found the repo-server. They sent a single gRPC request to the GenerateManifest service. The request set the --helm-command option to point at a script hosted in an attacker-controlled Git repository. The repo-server ran kustomize. Kustomize executed the script. The script dumped environment variables to a file. Among those variables was the Redis password. The attacker used that password to connect to Argo CD's Redis cache. The cache stores the deployment state. The attacker wrote a malicious Application manifest into the cache. On the next sync cycle, Argo CD read the poisoned cache and deployed the attacker's workload. The workload was a pod with a hostPath volume. That gave the attacker access to the node's filesystem. From there, they extracted the kubelet credentials and registered a new node in the cluster. The cluster was now theirs. The entire chain took 47 minutes from initial pod compromise to node registration. The team had no alerts. The attacker never touched the Git repository. The GitOps promise was intact. The cluster was not.

Related Articles

Your AI Agent Has More Privilege Than the CEO: Why the Executor Is the Only Boundary That Matters

Your AI Agent Has More Privilege Than the CEO: Why the Executor Is the Only Boundary That Matters

Vulnox assessment data shows that in 9 of 11 enterprise agent deployments, the agent runtime had write access to its own identity provider. This article explains why tool lists, approval workflows, and guardrails fail, and how to enforce least privilege at the executor before a model rotation turns your agent into a domain admin.

The SSH Key That Nobody Killed Your Crypto Mining Response

The SSH Key That Nobody Killed Your Crypto Mining Response

This article walks through a real breach where a single unmanaged SSH key let a worm encrypt your network. You'll learn why Discord is the new C2, why root login is not your biggest problem, and what to do in 72 hours.

The Autonomous Agent Insider Crisis: What Your Detection Models Are Missing

The Autonomous Agent Insider Crisis: What Your Detection Models Are Missing

A Trellix survey of 600 CISOs shows 95% of companies report AI agent insider incidents. Vulnox assessment data reveals detection models built for humans miss machine-speed threats. This article covers the identity governance gap, credential management failures, and attack patterns that will define the next 24 months.

Ready to Secure Your Digital Assets?

Get a comprehensive vulnerability assessment for your website today.