Join our Newsletter — 33% off our NHI Course
Home› Guides› GitOps Security Guide: Argo CD and Flux
Guide Cloud Security

GitOps Security Guide: Argo CD and Flux

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 17 min read
On this page

In a GitOps estate, whoever can change the Git repository or the GitOps controller can change production, so the repository, the controller and the credentials between them become your most privileged attack surface. Argo CD and Flux, both CNCF projects, pull desired state from Git (or OCI and Helm repositories) and reconcile Kubernetes clusters towards it continuously. That model removes cluster credentials from CI pipelines, which is a real security gain, but it concentrates trust in a controller that often runs with cluster-admin rights and holds repository, cluster and decryption credentials. This guide covers the OpenGitOps principles and what they mean for security, hardening Argo CD and Flux (SSO, RBAC, projects and tenancy), repository and cluster credentials as non-human identities, secrets handling with Sealed Secrets, External Secrets and SOPS, commit verification, drift and rollback, and detection. It is written for platform engineers, Kubernetes administrators and security architects.

Key takeaways

  • Git write access is production access. Branch protection, required reviews and signed commits on the configuration repository are deployment controls, not developer hygiene.
  • The controller is a privileged identity. Argo CD's default install uses a cluster-admin-level role, and Flux binds cluster-admin to kustomize-controller and helm-controller; scope both down for multi-team clusters.
  • Repository and cluster credentials are NHIs. Deploy keys, tokens, GitHub App keys and the argocd-manager service account token need owners, least privilege, rotation and revocation like any other machine credential.
  • Keep secret values out of the controller. Argo CD's own documentation strongly recommends populating secrets on the destination cluster rather than injecting them during manifest generation.
  • Tenancy is opt-in. Argo CD's default project and Flux's default settings are permissive; AppProjects, impersonation and the Flux lockdown flags have to be configured deliberately.

How GitOps changes the threat model

OpenGitOps defines four principles in version 1.0.0: desired state is declarative; it is versioned and immutable with a complete history; it is pulled automatically by software agents; and it is continuously reconciled by agents that compare actual and desired state. Each principle has a security consequence.

  • Declarative and versioned: every change is a commit, so the repository history is the change record (NIST SP 800-53 CM-3) and the baseline (CM-2). That only holds if history cannot be rewritten: block force pushes and branch deletion on deployment branches.
  • Pulled automatically: the controller runs inside or next to the cluster and fetches state, so CI pipelines no longer need a kubeconfig. Pipeline compromise no longer equals cluster compromise, but repository compromise does.
  • Continuously reconciled: manual changes made with kubectl are reverted (if self-heal or equivalent is enabled), which removes a common persistence route but also reverts emergency fixes made outside Git.

The trust boundary moves from "who can run kubectl" to "who can merge to the config repository, who can change controller configuration, and what the controller's own identity is allowed to do". MITRE ATT&CK techniques that map to this boundary include Compromise Software Supply Chain (T1195.002) through a poisoned repository or chart, Credentials In Files (T1552.001) for keys committed to Git, Container API (T1552.007) for secrets read through the Kubernetes API, Deploy Container (T1610) once an attacker controls what the controller applies, and Cloud Accounts (T1078.004) for stolen cloud credentials used by controllers. See the CI/CD Security Guide for the pipeline side, which this guide does not repeat.

Argo CD and Flux: security-relevant differences

AreaArgo CDFlux
User interface and APIWeb UI and API server; API accepts JWTs only (local admin, SSO via Dex or an OIDC provider, project role tokens)No built-in UI; users act through Git and the Kubernetes API, so Kubernetes RBAC governs access
Authorisation modelOwn RBAC in argocd-rbac-cm (Casbin syntax) plus AppProject rolesKubernetes RBAC; flux-view and flux-edit ClusterRoles aggregate into view, edit and admin
Default controller privilegeCluster-admin-level ClusterRoles for argocd-application-controller and argocd-server; argocd-manager-role on external clusterscluster-reconciler binds cluster-admin to kustomize-controller and helm-controller
Tenant isolationAppProjects: sourceRepos, destinations, resource allow and deny lists, project rolesImpersonation of a tenant service account via spec.serviceAccountName; lockdown flags
Rendering riskrepo-server runs Helm, Kustomize and config management plugins; disable tools you do not usekustomize-controller and helm-controller render; --no-remote-bases blocks Kustomize remote bases
In-cluster data storesRedis caches generated manifests; plain connection by default unless TLS is configuredNo equivalent shared cache in the core controllers
Source verificationGPG commit verification per project (see Source Integrity Verification in the Argo CD docs)spec.verify.mode HEAD, Tag or TagAndHEAD with PGP (.asc) or SSH (.sshpub) keys

Choosing between them is rarely a security decision. Argo CD gives a UI and fine-grained application RBAC, which suits organisations where many teams need self-service visibility, but adds an internet-facing API server, local accounts and a token issuer to secure. Flux has a smaller surface and relies on Kubernetes RBAC, which suits platform teams that already manage RBAC well and do not want another authorisation layer. A small team on one cluster can start with SSO and branch protection; multi-tenant or regulated estates need the tenancy controls below from day one.

Hardening Argo CD access: SSO, local accounts and RBAC

  1. Use SSO and disable the admin account. Configure an existing OIDC provider under oidc.config in argocd-cm (or Dex under dex.config), map identity provider groups to roles, then set admin.enabled: "false". The admin password is a bcrypt hash in argocd-secret; changing it revokes existing admin tokens.
  2. Set a minimal default role. policy.default in argocd-rbac-cm applies to every authenticated user and cannot be overridden by a deny rule, so set it to an empty or minimal custom role rather than role:readonly when read access exposes sensitive manifests. Leave users.anonymous.enabled off.
  3. Write policy as code. Policies use p, <subject>, <resource>, <action>, <object>, allow|deny and g, <group>, <role>; deny always wins. Application objects are scoped as <project>/<app>. Validate changes with argocd admin settings rbac validate and test with argocd admin settings rbac can in the pull request that changes them.
  4. Restrict high-impact actions. exec, create gives an interactive shell in pods; override lets a user sync arbitrary manifests that are not in Git; clusters, repositories and projects create, update and delete change the trust model itself. Reserve these for a small platform group, and from v3.2 consider application.sync.requireOverridePrivilegeForRevisionSync: 'true' so syncing an arbitrary revision also needs override.
  5. Control local accounts. Local users are defined as accounts.<name> with capabilities apiKey and/or login. Give automation accounts apiKey only, and disable unused accounts with accounts.<name>.enabled: "false".
  6. Protect the API server. Do not expose the UI or API to the internet without SSO in front; set --tlsminversion 1.2; log client IP addresses at the ingress or proxy, because Argo CD does not log them.

An unauthenticated cluster management interface has caused real incidents: in the Tesla Kubernetes cryptojacking incident, a Kubernetes console without a password let attackers find AWS credentials in a pod and run cryptominers. A GitOps UI with write or exec rights is at least as dangerous.

Projects, tenancy and controller privilege

Argo CD AppProjects

  • Do not use the default project: default is created automatically, cannot be deleted and allows any source repository, any destination and every cluster-scoped kind. Lock it down (empty sourceRepos and destinations, namespaceResourceBlacklist of */*) and create one project per team or trust zone.
  • Constrain sources and destinations: list exact repositories in sourceRepos and exact server/namespace pairs in destinations; negation with ! is supported. Set permitOnlyProjectScopedClusters: true where teams register their own clusters.
  • Constrain kinds: keep clusterResourceWhitelist empty for application teams so they cannot create ClusterRoles, ClusterRoleBindings, CustomResourceDefinitions or admission webhooks; use namespaceResourceBlacklist for kinds such as ResourceQuota or NetworkPolicy that the platform team owns.
  • Project role tokens: argocd proj role create-token issues JWTs that have no expiry by default; always pass -e and revoke with argocd proj role delete-token.

Flux multi-tenancy lockdown

FlagControllersEffect
--no-cross-namespace-refs=truekustomize, helm, notification, image-reflector, image-automationA tenant cannot reference another tenant's sources or events
--no-remote-bases=truekustomize-controllerKustomize remote bases are blocked, so only Flux sources change cluster state
--default-service-account=defaultkustomize, helmObjects without spec.serviceAccountName run as the tenant namespace's permissionless default account

With these flags, Flux reconciles each tenant through the Kubernetes impersonation API as the tenant's own service account, so that account's RoleBindings, not cluster-admin, bound what a tenant repository can create. Reconciliation fails if the binding is missing, which is the desired failure mode.

Reduce the controller's own rights

Argo CD documents that write access should be limited to the namespaces and kinds it should manage: edit argocd-manager-role on external clusters and the argocd-server and argocd-application-controller ClusterRoles locally, and use resource exclusions to hide kinds entirely. Running the GitOps control plane on a dedicated management cluster limits the blast radius if its namespace is compromised. See the Kubernetes RBAC Guide and Kubernetes Security Guide for the cluster controls underneath.

Where identity fits: repository and cluster credentials

A GitOps controller authenticates to every source and every cluster it manages. Each credential is a non-human identity with standing access to production.

CredentialWhere it livesRiskBetter option
HTTPS username and token or PATArgo CD Secret labelled argocd.argoproj.io/secret-type: repository; Flux Secret with username/password or bearerTokenOften a person's PAT with access to every repository they can seeGitHub App or a read-only deploy token scoped to one repository
SSH deploy keySecret (identity plus known_hosts in Flux; argocd-ssh-known-hosts-cm in Argo CD)No expiry; write-enabled keys can pushRead-only deploy key per repository, pinned host keys
GitHub AppApp ID, installation ID or owner, private key (Argo CD repo creds; Flux spec.provider: github)Private key compromise grants every installed repositoryInstall on config repositories only, contents read-only
Cloud workload identityAzure workload identity (both tools), AWS for CodeCommit in Flux, KMS access for SOPSOver-broad cloud role bound to the controller service accountFederated identity scoped to one repository, registry or key
External cluster tokenSecret in the argocd namespace for the argocd-manager service account; non-expiring on Kubernetes 1.24+Long-lived bearer token with cluster-admin-level rightsCloud IAM authentication (for example EKS via get-token) and a scoped ClusterRole
  • Credential templates: Argo CD credential templates apply one credential to every repository under a URL prefix (longest prefix wins). A template on an organisation prefix silently grants the controller read access to new repositories.
  • Never skip verification: --insecure-skip-server-verification disables TLS and SSH host key checks; the Argo CD documentation limits it to non-production.
  • Rotation and revocation: for an external cluster, rotate by deleting the token Secret on the managed cluster and re-running argocd cluster add; revoke by deleting the ServiceAccount, ClusterRole and ClusterRoleBinding and running argocd cluster rm.

Treat these credentials in your NHI inventory with an owner and rotation date. See the Kubernetes NHI Security Guide and the Workload Identity Federation Guide.

Secrets in GitOps: Sealed Secrets, External Secrets and SOPS

Plain Kubernetes Secrets cannot be committed to Git. Argo CD's documentation strongly recommends populating secrets on the destination cluster (Sealed Secrets, External Secrets Operator, Secrets Store CSI Driver, Vault Secrets Operator) rather than injecting them during manifest generation with a plugin, because injection gives Argo CD access to the values and stores generated manifests in plaintext in Redis.

ApproachWhat is in GitKey or credential to protectChoose when
Sealed SecretsEncrypted SealedSecret created with kubesealController sealing keys (renewed about every 30 days; old keys kept)No external secret store; small number of clusters
External Secrets OperatorExternalSecret reference only; values stay in the providerCredentials in SecretStore/ClusterSecretStore; operator can write secrets in all namespacesA central secrets manager or cloud vault already exists; rotation needed
SOPS with FluxValues encrypted with age, OpenPGP or cloud KMS; Flux decrypts via spec.decryption.provider: sopsPrivate key Secret in flux-system, or KMS access for kustomize-controllerFlux estates wanting encrypted values reviewable in pull requests
Plugin injection at render timePlaceholdersSecrets readable by repo-server; plaintext in RedisAvoid; if unavoidable, restrict network access to Redis and repo-server
  • Sealed Secrets: keep the default strict scope (bound to name and namespace) rather than cluster-wide; back up sealing keys after every renewal; kubeseal --re-encrypt re-seals with the newest key, but key renewal is not secret rotation. If a sealing key leaks, rotate the underlying credentials.
  • External Secrets: give the operator's provider credentials least privilege, prefer namespaced SecretStore objects for tenants over a shared ClusterSecretStore, and use admission policy (OPA Gatekeeper or Kyverno) to stop tenants referencing stores they do not own.
  • SOPS: encrypt only data and stringData with encrypted_regex: ^(data|stringData)$, back up the private key and remove it from laptops.

See the Secrets Management Guide for vault design and rotation.

Source integrity: commits, charts and rendering

  • Verify commits: in Flux set spec.verify.mode: HEAD (or Tag/TagAndHEAD for tag-based releases) with trusted keys in a Secret; in Argo CD enable GPG signature verification for projects. Unsigned or wrongly signed revisions then fail to reconcile.
  • Protect the repository: required reviews on deployment branches, CODEOWNERS for platform paths (projects, RBAC, controller config), no force push, and separate repositories or paths for platform and application teams. See the GitHub and GitHub Actions Security Guide.
  • Control what rendering can reach: Helm chart dependencies and remote Kustomize bases can pull from repositories outside the allow-list; pin versions, mirror charts internally and disable rendering tools you do not use. CVE-2022-24348 (CVSS 3.1 7.7, CWE-22) was a path traversal in Argo CD's Helm handling before 2.1.9 and 2.2.4 that could expose files such as credentials in YAML from other applications on the repo-server.
  • Verify the controllers themselves: Flux controller and CLI images are signed with Cosign and ship an SPDX SBOM; verify signatures in admission before the GitOps control plane is upgraded. See the Software Supply Chain Security Guide.

Drift, rollback and emergency change

Drift is any difference between Git and the cluster: an attack (a manual RoleBinding), an emergency fix, or expected mutation by admission controllers.

  • Decide on self-heal per environment: automatic correction in production removes attacker persistence through kubectl but also reverts emergency fixes; where self-heal is off, alert on OutOfSync and require a commit within an agreed window.
  • Roll back through Git: revert the commit so history stays the record. A UI rollback or sync to an older revision leaves Git and the cluster disagreeing, and with automated sync it is undone at the next reconciliation.
  • Prune deliberately: pruning deletes resources removed from Git. A malicious or mistaken commit that deletes manifests can delete workloads, so require review on deletions and consider disabling prune for stateful or cluster-scoped resources.
  • Break-glass: keep a documented, audited path to suspend reconciliation (Flux suspend, Argo CD disabling automated sync) during an incident, and make suspension itself an alert. See the Break-Glass Account Guide.

Implementation: a phased approach

  1. Inventory (week 1): list every GitOps instance, the clusters it manages, every repository and cluster credential, the controllers' ClusterRoles, and every user with write access to config repositories.
  2. Close the front door: SSO, admin disabled, minimal policy.default, API not internet-facing, branch protection on deployment branches. Test by logging in as a non-platform user and confirming read-only or no access.
  3. Introduce projects and tenancy in audit first: create AppProjects or Flux tenant service accounts that mirror today's access, deploy in a non-production cluster, watch for sync failures, then tighten sourceRepos, destinations and kinds. Roll back by moving the application back to its previous project; keep the change in Git so the revert is one commit.
  4. Replace credentials: swap personal PATs for GitHub Apps or read-only deploy keys, and static cluster tokens for cloud IAM where available; set expiry on every project token.
  5. Move secrets out: migrate plugin-injected secrets to destination-cluster tooling; pilot one application, confirm rotation works without a sync, then migrate the rest.
  6. Enforce integrity: turn on commit verification for one project, confirm signed commits reconcile and unsigned ones fail, then extend.
  7. Scope controller privilege last: restrict ClusterRoles once you know which kinds each instance really manages; a too-narrow role surfaces as sync errors, which you roll back by restoring the previous ClusterRole from Git.

Detection and measurement

  • Controller logs: Argo CD tags security-relevant log lines with a security field (1 low to 5 emergency) and a CWE where applicable; forward them to the SIEM. Not all security events are tagged yet, so also collect API request logs.
  • Kubernetes Events: Argo CD emits Events for application activity naming the actor where possible; persist them, because Kubernetes keeps Events only briefly.
  • Kubernetes audit log: alert on changes to argocd-cm, argocd-rbac-cm, AppProjects, Flux Kustomization or GitRepository objects outside Git, exec into pods via the GitOps API, and new Secrets of type repository or cluster.
  • Git provider audit: branch protection changes, new deploy keys, GitHub App installations and force pushes on config repositories.
MetricTarget
Applications in the Argo CD default project or Flux tenants without impersonation0
Repository and cluster credentials that are personal tokens or have no expiryFalling to 0
Production deployments from verified (signed) commits100%
Mean time an application stays OutOfSync without an approved reasonUnder the agreed window (for example 24 hours)
Users with exec, override or cluster/repository write rightsNamed platform group only, reviewed quarterly
Secrets injected at render time0

Common failure patterns

  • Everything in the default project: any team that can create an Application can deploy any repository to any namespace, including cluster-scoped kinds.
  • A personal PAT as the repository credential: the controller inherits a person's access to every repository and breaks when they leave; nobody rotates it.
  • Exposed UI with broad defaults: an internet-facing API server, policy.default: role:readonly and SSO for the whole company lets anyone read every manifest and configuration, and sometimes exec.
  • Secrets injected at render time: plaintext manifests sit in Redis, which is unencrypted by default, so a foothold in the namespace yields every secret.
  • Unprotected deployment branch: branch protection exists on the application repository but not on the config repository the controller watches.
  • Controller left at cluster-admin across shared clusters: a tenant repository can create ClusterRoleBindings, so one compromised repository becomes cluster takeover.

Framework mapping

Control areaNIST SP 800-53 Rev. 5NIST CSF 2.0MITRE ATT&CK
Git as baseline and change recordCM-2, CM-3, SA-10PR.PS-01T1195.002
Who can change config and controllersCM-5, AC-6PR.PS-01T1610, T1609
Repository and cluster credentialsIA-5PR.AA-01T1552.001, T1078.004
Secrets handlingIA-5, SC-28PR.DS-01T1552.007
Commit and image verificationSI-7, SR-4PR.PS-02T1195.002
Audit of GitOps actionsAU-2, AU-12DE.CM-09n/a

Practitioner checklist

  • Disable the Argo CD admin account after SSO is working and set a minimal policy.default.
  • Lock down the default AppProject and give every team its own project with explicit sources, destinations and kinds.
  • Enable --no-cross-namespace-refs, --no-remote-bases and --default-service-account on multi-tenant Flux installs.
  • Replace personal tokens with GitHub Apps or read-only deploy keys, and record every controller credential in the NHI inventory with an owner.
  • Set expiry on all Argo CD project role tokens and prefer cloud IAM over static cluster tokens.
  • Keep secret values on the destination cluster with Sealed Secrets, External Secrets or SOPS; never inject at render time.
  • Protect config repository branches and require signed commits verified by the controller.
  • Scope the controller's ClusterRoles to the kinds and namespaces it manages.
  • Forward controller logs, Kubernetes Events and audit logs to the SIEM and alert on configuration changes outside Git.
  • Rehearse rollback by Git revert and the break-glass suspend procedure.

Standards and references

Related NHI Mgmt Group resources: CI/CD Security Guide · Kubernetes Security Guide · Secrets Management Guide · Kubernetes NHI Security Guide · Infrastructure as Code Security Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.