The GitOps Trust Model

GitOps is built on a simple principle: the desired state of your system is stored in Git, and an operator continuously reconciles the actual state with the desired state. ArgoCD and Flux implement this for Kubernetes β€” they watch one or more Git repositories and apply any changes to the cluster automatically. The appeal is enormous: all changes are auditable via git history, rollbacks are a revert commit, and there is no need for humans to have direct kubectl access to production clusters.

The security model that underpins this is that Git is trusted. The GitOps operator has cluster-admin equivalent permissions and will apply whatever it finds in the configured repository path. This makes the repository the most security-critical artefact in the system β€” not the cluster itself. Most organisations have robust cluster access controls while leaving their GitOps repositories with comparatively weak branch protection and contributor permission models.

The trust inversion: In traditional deployments, cluster credentials are the crown jewels. In GitOps, the config repository is. An attacker with write access to the repository does not need cluster credentials β€” ArgoCD will execute the attack for them.

Repository as Infrastructure

When a Git repository directly controls production infrastructure, all the security properties of infrastructure access must apply to repository access. This means branch protection that requires reviews, signed commits to verify identity, audit logging of every push, and least-privilege policies for who can merge to the branches that ArgoCD or Flux watches.

In practice, many GitOps repositories are treated as configuration storage rather than as access control boundaries. Engineers who would never be given direct kubectl access to a production cluster are granted write access to the repository whose production/ path ArgoCD syncs from β€” because "it's just Kubernetes YAML." The privilege escalation from repository write access to full cluster access is direct and automated.

Multi-Cluster GitOps and Blast Radius

ArgoCD is frequently deployed in a hub-and-spoke model where a single ArgoCD instance manages multiple clusters. The hub ArgoCD has credentials for every spoke cluster. An attacker who compromises the ArgoCD control plane β€” or the Git repositories it syncs from β€” can simultaneously push workloads to every managed cluster. In a multi-region deployment, that is a simultaneous compromise of every region.

ArgoCD Attack Patterns

ArgoCD has a history of security vulnerabilities. CVE-2022-24348 allowed path traversal in Helm chart values files to read arbitrary files from the ArgoCD server, including secrets. CVE-2023-40025 enabled credential harvesting from the ArgoCD API server. The pattern in multiple CVEs is the same: the ArgoCD server processes content from Git repositories with excessive trust, and malicious repository content can influence the server itself.

Repo WriteAttacker pushes malicious YAML or Helm values to a watched branch
Auto SyncArgoCD detects the commit and begins reconciliation
Payload AppliedMalicious manifest deployed β€” privileged pod, RBAC change, or secret exfiltration job
Cluster ImpactFull cluster compromise via privileged workload or credential harvesting

ApplicationSet Controller Abuse

ArgoCD's ApplicationSet controller dynamically generates Application resources based on templates β€” for example, one Application per environment or per team directory. If the ApplicationSet template reads values from the repository it manages, an attacker who can write to that repository can inject arbitrary Application configurations, including source repository overrides that point ArgoCD at an attacker-controlled repository containing malicious manifests.

Secrets in GitOps Repositories

The GitOps principle β€” everything in Git β€” creates an immediate tension with secrets management. Kubernetes Secret manifests contain base64-encoded data. Base64 is not encryption. A GitOps repository that contains Secret manifests exposes every credential in plain text to anyone with repository read access. This is a systematic issue: every developer with access to the config repo can read every service credential in every environment the repo manages.

The standard mitigations are Sealed Secrets (encrypt secrets with a cluster-specific key before committing) and External Secrets Operator (store references to secrets rather than the secrets themselves, with the operator fetching at runtime from Vault or cloud secrets managers). Both approaches work but require deliberate adoption. Teams that simply commit Kubernetes Secret YAML directly to their GitOps repositories β€” which is the path of least resistance β€” are exposing credentials in version control.

Git history is permanent: Even if a plaintext Secret is removed from the current branch, it remains in the git history unless that history is explicitly rewritten. Every developer who cloned the repository before the remediation already has a copy of the secrets.

ArgoCD RBAC Bypass Techniques

ArgoCD has its own RBAC system, separate from Kubernetes RBAC, that controls who can sync, delete, and modify Applications. This system has been the subject of several vulnerabilities. Beyond discrete CVEs, the ArgoCD RBAC model has architectural weaknesses: the default ArgoCD installation creates a cluster-admin ClusterRoleBinding for the application controller, and RBAC policies that look restrictive at the ArgoCD level do not limit what the application controller can do with its Kubernetes permissions when it syncs a manifest.

An attacker who can create or modify an ArgoCD Application resource β€” either directly through the ArgoCD API or by pushing an Application manifest to a watched repository β€” can point it at any source repository and any destination cluster, regardless of what the ArgoCD RBAC policy says about which projects a user can access. The Application controller will process the sync with full cluster-admin permissions.

Hardening GitOps

  1. Apply production-grade branch protection to GitOps repositories: Require signed commits, at least two reviewers for merges to production branches, and restrict who can bypass these rules. Treat a merge to the production branch as equivalent to a production deployment β€” because it is.
  2. Never store plaintext secrets in GitOps repositories: Use Sealed Secrets, External Secrets Operator, or a similar solution that stores encrypted references rather than credentials. Scan your GitOps repositories for secrets on every commit.
  3. Scope ArgoCD's Kubernetes permissions: The application controller does not need cluster-admin. Define fine-grained ClusterRoles that limit it to the namespaces and resource types it actually manages. Use separate ArgoCD instances or projects with different service accounts for different environments.
  4. Enable ArgoCD's resource allowlist: Configure ArgoCD to only allow specific Kubernetes resource types in synced applications. Prevent syncing of ClusterRoleBindings, PodSecurityPolicies, and other high-privilege resource types unless explicitly required.
  5. Audit every sync operation: Every ArgoCD sync should produce an audit log entry that records the source commit hash, the resources created or modified, and the identity that triggered the sync. Alert on syncs that create privileged pods, modify RBAC resources, or access secrets.
  6. Disable auto-sync for production environments: Require manual sync approval for production clusters. Auto-sync in staging is acceptable; auto-sync in production means any merged commit immediately affects live users.

GitOps security is repository security: The most impactful security investment for a GitOps environment is hardening the repositories it watches β€” strong branch protection, secrets scanning, signed commits β€” rather than focusing exclusively on the cluster or the ArgoCD configuration.