Argo CD repo-server RCE: how manifest generation turns into cluster takeover
Synacktiv disclosed an unpatched flaw in Argo CD's repo-server that lets an unauthenticated request run code inside a Kubernetes cluster, provided the caller can reach an internal port. There is no fix and no CVE. The bug class is worth studying because the mistake is common: an internal microservice that trusts anyone who can reach it, plus a manifest step that treats an attacker-controlled Git repository as a place to run programs.
Where the bug lives
Argo CD is a GitOps continuous delivery controller. It reads Kubernetes manifests from Git and reconciles the cluster to match them. The heavy lifting of reading a repository and rendering manifests happens in a separate component, the repo-server. Other Argo CD components talk to it over an internal gRPC service. That service has no authentication. The design assumes the only callers are Argo CD's own pods, so it trusts every request that arrives.
The exposed method that matters is GenerateManifest. It accepts a repository reference and rendering options, then produces the manifests to apply. The problem is that some rendering paths are allowed to execute programs by design.
How manifest generation becomes code execution
Manifest rendering in Argo CD supports tools like Helm and Kustomize. Kustomize has an exec-plugin mechanism, and Helm has hooks; both are legitimate features for teams that need to shape manifests before they are applied. When a request to GenerateManifest points at a repository the attacker controls and selects a rendering option that shells out, the repo-server runs whatever script that repository provides. There is no separate exploit primitive to build. The feature runs the code; the attacker just supplies the input.
The vulnerability is not the renderer. It is an internal service that authenticates nobody and a build step designed to execute untrusted input. the recurring shape of pipeline RCE
Reaching the port
An attacker still needs a path to the repo-server's internal port. In many clusters that path is short. Argo CD's Helm chart ships with networkPolicy.create set to false, so by default no NetworkPolicy restricts which pods may talk to the repo-server. Kubernetes networking is flat unless a policy says otherwise: any pod can reach any service. So a single compromised pod, from an unrelated vulnerable web app, for example, is enough to send the crafted GenerateManifest request.
Synacktiv showed the pivot does not stop at code execution. From the repo-server they read Argo CD's Redis password out of an environment variable, connected to the Redis cache Argo CD uses for computed state, and poisoned the stored deployment data. Tampering with what the controller believes the desired state to be is a direct route to a full cluster takeover, because the controller will then apply the attacker's manifests with its own privileges.
Reproducing it safely
Keep this in a lab you own. The point is to see the trust assumption fail, not to touch anything live.
# 1. install Argo CD in a throwaway cluster (kind/minikube) kubectl create namespace argocd helm install argocd argo/argo-cd -n argocd # chart default: no NetworkPolicy # 2. from an attacker pod, confirm the internal port is reachable kubectl run probe --image=nicolaka/netshoot -it --rm -- \ nc -zv argocd-repo-server.argocd 8081 # 3. observe: the gRPC service answers with no credentials required # a GenerateManifest call pointing at a repo with an exec # rendering step runs that repo's script in the pod
The learning goal is the reachability check in step 2. If that connection succeeds from a pod that has nothing to do with Argo CD, the trust boundary is not enforced by the network, and every downstream assumption inside the repo-server is exposed.
Defending it
Because there is no patch, the containment is architectural. The fix is to stop treating "inside the cluster" as a trust boundary and enforce one at the network layer.
- Enable NetworkPolicies. Helm users must set
networkPolicy.create=trueso only Argo CD's own components can reach the repo-server and Redis ports. Deny east-west traffic to those services by default. - Segment Redis. Restrict who can reach the cache and rotate the password if a pod compromise is suspected; cache poisoning is the escalation step, not the entry.
- Reduce blast radius. Run Argo CD in its own namespace, scope its service account and RBAC to the minimum, and keep untrusted workloads off the same nodes where practical.
- Watch the input. Constrain which repositories Argo CD may render and treat rendering plugins that shell out as privileged features, not conveniences.
The takeaway for practitioners is the same one that recurs across pipeline compromises: an internal service is only internal if something enforces that. When the network is flat and the service authenticates nobody, "internal" is just a label, and any pod in the cluster is a valid caller.
Sources
- The Hacker News. "Unpatched Argo CD Repo-Server Flaw Could Let Attackers Take Over Kubernetes Clusters." Read the report
- Synacktiv. Vulnerability research disclosure on Argo CD repo-server. Synacktiv research
- Argo CD. "Security Considerations." Project documentation