← All field notes
argocd gitopskubernetesfor responders

The fix is in Git: stopping ArgoCD from re-syncing attacker manifests

Delete an attacker workload from a GitOps cluster and the controller re-creates it within minutes. The source of truth is Git, so that is where containment lives.

In GitOps the cluster is a mirror of Git, and the ArgoCD controller faithfully deploys whatever the tracked branch declares, with its own broad cluster privileges. That is exactly what an attacker abuses.

How the attack works

With ArgoCD admin access or leaked Git repo credentials, an attacker commits malicious manifests to the GitOps source of truth on the tracked branch, adding new workloads and an over privileged service account. The ArgoCD controller detects the new desired state and applies it to the cluster with its own rights. The synced workload runs, establishes a foothold, and beacons out to an external endpoint. The principal is the controller acting on manifests nobody reviewed, not a human at a kubectl prompt. In ATT&CK terms this is T1195, Supply Chain Compromise, paired with T1648, Serverless Execution.

Why it works

Anything that reaches the tracked branch is deployed with no provenance check, and the controller is over privileged. The desired state lives in Git, not the cluster, so the cluster is only ever a reflection of whatever Git declares.

How to fix it

The scenario teaches the non obvious move: deleting the workloads with kubectl loses, because the controller re-syncs from Git and re-creates them within minutes. Instead, revoke the ArgoCD access and rotate the leaked repo credentials, then revert the malicious commit in Git so the controller self heals back to known good. The durable fix is to require signed commits and enforce branch protection with mandatory manifest review, restrict who can write to the tracked branch, and scope the controller to least privilege. Scope impact by correlating the Git history, ArgoCD sync events, and the Kubernetes audit log over the window, because a revert stops recurrence but not what the workload already reached.

Practice it

We built this as a GraphLattice Range scenario so responders can rehearse fixing Git, not the cluster, when a GitOps controller turns on you.