InnoCTF
infra Bug-class explainer

Argo CD repo-server RCE: how manifest generation turns into cluster takeover

Abstract diagram of an unauthenticated request reaching an internal Kubernetes service and executing code
One reachable internal port, one command execution. Lab reproduction, isolated cluster.

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.

lab.sh
# 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.

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.

Disclosure: This article was researched and drafted with AI assistance and edited by the InnoCTF Editorial Team. It explains a publicly disclosed flaw for education and authorized testing only, and it does not target any live system. Reproduce only in infrastructure you own.

Sources

  1. The Hacker News. "Unpatched Argo CD Repo-Server Flaw Could Let Attackers Take Over Kubernetes Clusters." Read the report
  2. Synacktiv. Vulnerability research disclosure on Argo CD repo-server. Synacktiv research
  3. Argo CD. "Security Considerations." Project documentation