Back to blog

Argo CD v3.5.4: security assessment and upgrade planning checklist

Upgrading Argo CD means changing the platform that delivers changes to other environments. Version v3.5.4, released on October 6, 2026, contains eight security fixes, including four rated critical. This is a coordinated security update, not a single vulnerability fix or routine housekeeping. (github.com)

This guide proposes an operational approach: establish exposure, choose a patched release, test the relevant workflows, and execute a traceable change. The examples are illustrative. Adapt names, topology, and acceptance criteria before using them in production.

1. Understand the security changes

The following advisories were published on October 7, 2026. Track each issue by its GHSA identifier. Do not invent a CVE assignment or associate an unrelated vulnerability from an earlier release with this update. (github.com)

SeverityAdvisorySurface to assess
CriticalGHSA-9v9p-x54c-58gcKustomize remote references can execute commands in repo-server. (github.com)
CriticalGHSA-fw5c-w8rc-j7fxKustomize Helm rendering with --enable-helm can load repository-controlled plugins. (github.com)
CriticalGHSA-m3vr-7329-44wwJsonnet imports can read files accessible to repo-server outside the repository. (github.com)
CriticalGHSA-fmxq-cgp8-87wpPreDelete and PostDelete hooks can bypass AppProject restrictions. (github.com)
HighGHSA-4439-h7jw-5cjjConcurrent requests can bypass local login failure throttling. (github.com)
HighGHSA-j6cw-g6p4-7hchProxy URLs configured for SSH Git repositories can cause command injection. (github.com)
ModerateGHSA-g3ff-q88g-chrjProxy extension authorization can omit the namespace for Applications outside the Argo CD namespace. (github.com)
ModerateGHSA-w996-f2wq-x9c6Oversized OCI manifests can exhaust repo-server memory. (github.com)

Read-only access is not automatically harmless

For the Kustomize reference vulnerability, server-side generation of uploaded files requires only get on an existing Application. That path does not require Git write access or sync permission. Blocking egress also does not stop exploitation through local file:// transport. Upgrade rather than treating network isolation as an equivalent fix. (github.com)

The Jsonnet issue also exposes an upload path against directory Applications. Include diff consumers, integrations, and read-only users in the exposure review—not just administrators. (github.com)

Deep dive: Kustomize remote references (GHSA-9v9p-x54c-58gc)

Published October 7, 2026, the advisory covers unpatched releases from 2.7.0 onward; no v2 patch is listed. Inputs can arrive through Git or POST /api/v1/applications/manifestsWithFiles with only Application get permission. Local file:// transport bypasses egress controls, and commands can access process credentials. (github.com)

Updating Kustomize alone is insufficient. Pinning every execution to 4.5.7 is a limited mitigation, incompatible with 5.x features; available 5.x paths remain exposed. Upgrading Argo CD is the stated complete remedy. Do not mark an instance safe solely because its known repositories appear trustworthy: include users, automation identities, and read permissions in the assessment. (github.com)

What does the fix change? The issue concerns git fetch arguments: ref or version values could become options. The patch places a Git wrapper in the rendering process's PATH, inserts --end-of-options, and covers transitive references. Legitimate remote bases remain supported; unexpected fetch invocation formats are rejected. (github.com)

Argo CD and Kustomize versions are separate concerns: setting spec.source.kustomize.version selects a registered rendering tool and does not upgrade the controller. (argo-cd.readthedocs.io)

2. Select the right patched release

The advisories list 3.3.15, 3.4.10, and 3.5.4 as patched stable releases, alongside the 3.6.0-rc2 prerelease. Earlier affected ranges differ by issue. Check each advisory when assessing older installations; the absence of a patch for an old branch does not establish that the branch is unaffected. (github.com)

My planning recommendation is:

  • Running 3.5.x: assess an upgrade to 3.5.4.
  • Running 3.4.x or 3.3.x: consider the patched release on the same branch first, separating security response from feature migration.
  • Running an older branch: plan a move to a supported branch and review every intermediate upgrade guide.
  • Considering a prerelease: do not adopt a release candidate in production simply because its version number is higher.

The project supports the latest three minor versions with fixes. Recheck support status and advisories when executing the change. This article addresses a specific release; it is not a permanent version recommendation. (github.com)

3. Map the architecture and trust boundaries

Argo CD provides declarative continuous delivery for Kubernetes. The argocd-server exposes the API and user interface, repo-server fetches sources and generates manifests, and application-controller compares desired state with cluster state and performs reconciliation. A simplified flow is:

Git or registry → repo-server → application-controller → Kubernetes API

User or automation → argocd-server → authorized operations

These components have different responsibilities and access surfaces. (argo-cd.readthedocs.io)

An Application describes a deployment. An AppProject constrains sources, destinations, and resource types, among other settings. Project restrictions complement role-based access control, or RBAC; they do not replace the Kubernetes permissions attached to the controller's destination credentials. (argo-cd.readthedocs.io)

The deletion-hook vulnerability matters particularly in shared environments: its reach is bounded by the controller's destination-cluster credentials, rather than the intended restrictions of the vulnerable AppProject. (github.com)

4. Build an installation and exposure inventory

Create one record per installation, including owner, environment, version, artifact source, and remediation decision. A proposed checklist:

  • [ ] Confirm the Kubernetes context and namespace.
  • [ ] Record desired images and running containers' imageID values.
  • [ ] Identify installation ownership: Helm, Kustomize, manifests, or an operator.
  • [ ] Inventory Applications, AppProjects, ApplicationSets, and destination clusters.
  • [ ] List renderers, plugins, and custom images.
  • [ ] Identify who can modify repositories, credential templates, and policies.
  • [ ] Record external API access, local accounts, and read-only integrations.
  • [ ] Assign an owner and deadline to each affected installation.

Start with Kubernetes inspection commands, adapting the namespace and checking the context before making changes: (kubernetes.io)

kubectl config current-context
kubectl -n argocd get deployments,statefulsets,pods
kubectl -n argocd get pods \
  -o custom-columns='POD:.metadata.name,IMAGES:.spec.containers[*].image,IMAGE_IDS:.status.containerStatuses[*].imageID'
kubectl -n argocd get applications.argoproj.io
kubectl -n argocd get appprojects.argoproj.io
kubectl -n argocd get applicationsets.argoproj.io

Translate the advisories into concrete questions: Is --enable-helm configured? Is Jsonnet used? Who can configure SSH proxies? Are proxy extensions and Applications in other namespaces both enabled? Who controls the OCI registries? Record evidence rather than relying on informal assurances. (github.com)

Also review kustomize.path.*, build options, and per-Application tool selections. Inventory Config Management Plugins (CMPs) as separate execution paths requiring their own tests; do not assume a custom plugin automatically receives native-path protection. (argo-cd.readthedocs.io)

For initial triage of Kustomize references, search tracked configuration files:

git grep -nE 'https?://|ssh://|git@|file://|[?&](ref|version)=' \
  -- '*kustomization.yaml' '*kustomization.yml' '*Kustomization'

Treat this as a heuristic, not proof that exposure is absent. Use the findings to build the test matrix, review dependencies, and identify areas requiring manual investigation.

5. Separate temporary containment from remediation

If an immediate upgrade is impossible, I recommend a separate emergency containment change with security approval and a short expiration period.

Removing --enable-helm closes the path described in the configHome advisory, but can interrupt Applications that depend on that integration. It does not fix the other vulnerabilities. (github.com)

For local login exposure, disabling local accounts and using centralized authentication can reduce the relevant attack surface. This particular issue does not affect Single Sign-On, or SSO, and OpenID Connect, or OIDC, login flows. (github.com)

As an incident-response recommendation, preserve evidence if unexpected execution or file exposure is suspected. Investigate credentials accessible to the process and plan their rotation. Replacing an image is not evidence that previously obtained access has been revoked.

6. Establish backups and approval gates

Before the maintenance window, name the person who can stop the change, identify who authorizes recovery, and agree on the evidence required to close the work.

Argo CD provides administrative export functionality. Use a CLI compatible with the installation and verify the namespace: the documentation warns that exporting from the wrong namespace may not fail explicitly. (argo-cd.readthedocs.io)

umask 077
argocd admin export -n argocd > argocd-backup.yaml

Treat the output as sensitive. I recommend immediate encryption, restricted access, and a restoration test in an isolated environment. Preserve Helm values, overlays, certificates, external configuration, and image references as well. The test should establish that the platform can be rebuilt; merely having a backup file is not a sufficient acceptance criterion.

Keep platform recovery separate from application recovery. PostgreSQL, persistent volumes, and external services need their own backup and restoration procedures.

Proposed minimum test matrix

AreaAcceptance criterion
AuthenticationSSO and emergency access work; unauthorized users remain blocked
AuthorizationBoth allowed and denied actions are tested using dedicated identities
RenderingThe organization's Helm, Kustomize, Jsonnet, and plugin workflows produce expected manifests
ReconciliationA canary Application converges without unexpected changes
DeletionHooks are exercised only through a disposable Application
IntegrationsRepositories, webhooks, notifications, and extensions are exercised
RecoveryThe rebuild procedure can run independently of Argo CD itself

7. Test compatibility changes explicitly

Two behavior changes deserve dedicated coverage:

  1. Kustomize no longer honors helmGlobals.configHome; builds relying on plugins registered through that directory can fail.
  2. OCI manifests larger than 4 MiB are rejected. This is a limit on the manifest document, not the extracted artifact size. (github.com)

If the change also moves from 3.4 to 3.5, review the Helm 4 migration, UI extensions, gRPC clients, and repo-server TLS configuration separately. Do not attribute those minor-version changes to patch 3.5.4. (argo-cd.readthedocs.io)

8. Execute the upgrade through the existing ownership model

Preserve the installation's management mechanism. For Helm, review the chart version, rendered images, and handling of custom resource definitions, or CRDs. For operator-managed installations, follow the operator's supported upgrade path. Avoid indiscriminately applying upstream manifests over resources owned by another tool.

For manifest-based installations, the following example only downloads the release's high-availability, or HA, variant. Review it and incorporate your overlays before applying anything. The release also provides a non-HA variant. (github.com)

curl --fail --location \
  https://raw.githubusercontent.com/argoproj/argo-cd/v3.5.4/manifests/ha/install.yaml \
  --output argocd-v3.5.4-ha.yaml

In this proposed workflow, rendered-argocd.yaml is your reviewed configuration output, not an unmodified upstream file applied blindly. Follow the upgrade guide and investigate field-ownership conflicts before considering --force-conflicts. (argo-cd.readthedocs.io)

kubectl diff -n argocd --server-side -f rendered-argocd.yaml
kubectl apply -n argocd --server-side -f rendered-argocd.yaml

Run these as separate steps. Exit code 1 from kubectl diff indicates differences, not necessarily an execution error. Successful application of manifests is not sufficient evidence to approve the change. (argo-cd.readthedocs.io)

My recommended promotion order is representative staging, a canary installation, and then remaining installations in batches. Avoid improvising blue/green by allowing two independent controllers to manage the same resources.

9. A complete defense-in-depth policy example

The following policy restricts TCP ingress to repo-server port 8081 to server and application-controller pods in the same namespace. It is a starting point—not a universal policy or a replacement for patching. NetworkPolicy requires a supporting network implementation, and permissions from multiple policies are additive. (kubernetes.io)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: repo-server-restricted-ingress
  namespace: argocd
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: argocd-repo-server
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: argocd-server
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: argocd-application-controller
      ports:
        - protocol: TCP
          port: 8081

Adapt the namespace, labels, and actual callers. Add dedicated monitoring access where needed. This policy neither restricts egress nor establishes cryptographic identity. An existing permissive policy can preserve access you intended to remove. Test connectivity before promotion. (kubernetes.io)

Complement network controls with Transport Layer Security, or TLS, least privilege, and review of mounted Secrets. Restrict controller Kubernetes permissions and administrative access to the namespace. Do not introduce hardcoded credentials or broad permissions to work around post-upgrade errors. (argo-cd.readthedocs.io)

10. Production readiness, capacity, and cost

A single replica and simplified monitoring can be sufficient for isolated development testing. For production, I recommend validating redundancy per component, spreading pods across failure domains, and reserving replacement capacity during rollout. Controller scaling must account for its sharding model; do not copy repo-server replica policies to every component. (argo-cd.readthedocs.io)

Set requests from measured usage and choose limits that accommodate real rendering workloads. Requests influence scheduling; memory limits can result in container termination, while CPU limits can cause throttling. Missing requests and arbitrarily low limits are different operational risks. (kubernetes.io)

A PodDisruptionBudget, or PDB, helps constrain voluntary disruptions. It does not guarantee availability during a zone failure or directly govern a Deployment's rolling update. Combine it with a suitable rollout strategy and sufficient capacity. (kubernetes.io)

For FinOps, I suggest tracking cost per installation, idle capacity, log retention, and registry traffic. Avoid placing the entire critical platform on Spot capacity without tested interruption tolerance. An On-Demand baseline may be a conservative choice, not a universal requirement. Revisit sizing after caches have stabilized.

11. Observe the rollout and validate behavior

Use Prometheus and Grafana to track argocd_app_info, argocd_app_reconcile, and argocd_app_sync_total. Correlate these with API availability, rendering errors, CPU, memory, restarts, and cluster connectivity. Alert on meaningful deviations from the installation's baseline rather than prescribing one threshold for every environment. (argo-cd.readthedocs.io)

I recommend centralized logs in Loki or an equivalent system, with access controls and an explicit retention policy. Correlate Application, Git revision, and change timestamp. Traces can provide additional detail when instrumented, but basic outage detection should not depend on them.

After authenticating the CLI, substitute your canary Application name:

argocd app get platform-canary --hard-refresh
argocd app wait platform-canary --sync --health --timeout 300

The first command requests a refresh that includes the manifest cache. The second waits for the requested states. The 300-second timeout is illustrative, not a service-level objective. Perform any authorized sync through your normal process; app wait does not initiate synchronization. (argo-cd.readthedocs.io)

A rendering validation Application

An upgrade must demonstrate delivery-plane behavior, not just Ready Pods. The hypothetical example below (a payments API using a shared security base and environment overlays) assumes the platform-validation project, destination namespace, and repository access already exist. It uses the official declarative API. (argo-cd.readthedocs.io)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payments-render-validation
  namespace: argocd
spec:
  project: platform-validation
  source:
    repoURL: https://github.com/example-org/platform-config.git
    targetRevision: REPLACE_WITH_APPROVED_COMMIT_SHA
    path: apps/payments/overlays/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: payments-validation
  syncPolicy:
    automated:
      enabled: false

Replace the repository, commit SHA, path, project, and namespace: the placeholder does not identify a real commit. Automated synchronization is disabled on purpose; use the Application to inspect rendering before approving any deployment. Restrict allowed sources, destinations, and resource types in the AppProject (this manifest does not create those policies), and do not copy production credentials into staging.

For the test Application, request a hard refresh, which also refreshes the manifest cache, then inspect conditions, the resolved revision, and the diff. Do not start with a fleet-wide refresh. (argo-cd.readthedocs.io)

argocd app get payments-render-validation --hard-refresh
kubectl -n argocd get application payments-render-validation -o yaml
CaseRequired evidence
Local baseRendering matches the approved baseline
Public remote baseExpected revision resolves successfully
Private remote baseLeast-privilege authentication succeeds
Transitive componentDependencies are understood and reproducible
Kustomize with HelmRendering respects the release's restrictions
Custom image or CMPIts execution path has separate validation

Run security regression tests only in a disposable laboratory without real Secrets or production connectivity. A successful legitimate build verifies compatibility; it does not independently prove resistance to exploitation.

12. Troubleshooting

Substitute actual pod names. These commands help distinguish scheduling failures, restarts, and runtime errors. (kubernetes.io)

kubectl -n argocd get pods -o wide
kubectl -n argocd describe pod <pod>
kubectl -n argocd logs <pod> --all-containers=true --tail=200
kubectl -n argocd logs <pod> -c <container> --previous
kubectl -n argocd get events --sort-by=.metadata.creationTimestamp
  • Helm rendering fails → possible `configHome` dependency → inspect configuration and logs → migrate to a workflow compatible with the fix. Restoring vulnerable behavior is not a permanent solution. (github.com)
  • OCI source is rejected → manifest exceeds the new limit → inspect the registry's manifest document → correct artifact publication or its source. Adding memory does not remove this validation. (github.com)
  • Hooks fail after upgrading → previously bypassed restrictions may now apply → compare the resource with its AppProject → adjust the hook or approve a justified minimum permission. (github.com)
  • Private Kustomize base fails to authenticate → incompatible credentials → compare source and base identities → configure the minimum required access. (argo-cd.readthedocs.io)
  • Wrapper rejects a fetch → unexpected custom-tool invocation → inspect tool versions and arguments in the logs → fix the integration. Do not remove the protection. (github.com)
  • OOMKilled or rendering timeouts → inadequate capacity for concurrent builds → inspect memory, disk, and concurrency → tune resources and `--parallelismlimit` before extending timeouts. (argo-cd.readthedocs.io)
  • Application appears stale → cached state → inspect revision and conditions → hard-refresh only the Application being investigated. (argo-cd.readthedocs.io)
  • Pods remain Pending or restart → capacity, limits, or configuration may be responsible → inspect events and previous termination details → resolve the identified cause before proceeding. (kubernetes.io)

13. Plan recovery without normalizing vulnerability exposure

I recommend rolling forward when the alternative reintroduces a critical vulnerability. Define stop conditions before starting: loss of administrative access, widespread rendering failures, or unexpected application changes.

If a regression requires recovery, stop further changes, preserve evidence, and follow the rehearsed procedure. Reverting one image does not automatically restore configuration, CRDs, or integrations. Test administrative restoration with the appropriate version and context. (argo-cd.readthedocs.io)

An emergency return to a vulnerable release should require explicit risk acceptance, containment, an accountable owner, and a deadline. Do not close the incident until the security fix has been restored.

14. Practical scenario and architectural trade-offs

Hypothetical scenario: several teams share an Argo CD platform that deploys an API, background workers, and internal services.

Problem: the platform needs urgent remediation without turning the security window into a broad migration.

Solution: stay on the current minor branch when a patched release exists, test representative renderers, and promote by installation.

Proposed implementation: inventory, a tested backup, a disposable hook-testing Application, a functional canary, permission review, and a Git-tracked platform update.

Expected outcome: evidence of remediation and operational continuity—not an invented percentage improvement or a guarantee of zero downtime.

A shared installation can simplify standardization, but its concentration of trust deserves review. Separate installations by environment or security domain are an architectural option, with additional operating overhead. For a small team, a short, rehearsed runbook can be preferable to building another automation platform.

This checklist is most useful for critical, multitenant, or regulated installations. A disposable lab does not need a full enterprise change process. However, real credentials or production connectivity remove the justification for treating that lab as harmless.

Options to evaluate after the emergency

ApproachIntended benefitOperational trade-off
Reviewed remote basesCross-team reuseRequires dependency governance
Vendored basesMore explicit local reviewRequires an update process
Pre-rendered manifestsLess rendering work at the destinationMoves controls into CI and artifact handling
Instances separated by trust domainStronger isolation boundariesMore maintenance and capacity

These are architectural proposals, not replacements for patching a vulnerable instance. For a small application portfolio owned by one team, avoid extra plugins and instances without measurable benefit.

Practical summary

  • Assess each advisory and its prerequisites separately.
  • Choose a patched release without unnecessarily expanding migration scope.
  • Test denied actions as well as successful login and UI access.
  • Preserve installation ownership and verify running artifacts.
  • Separate containment, patching, investigation, and credential rotation.
  • Close the change with evidence and a rehearsed recovery path.

Conclusion

Treat the Argo CD v3.5.4 upgrade as a security change to the software delivery chain. For DevOps, SRE, and Platform Engineering teams, success means more than changing an image tag: it means demonstrating that the platform still delivers software within approved trust boundaries.

A mature response balances urgency with representative testing and verifiable recovery. That balance makes GitOps an operationally sustainable practice rather than a critical dependency without a maintenance plan.

Want to apply this to your environment?

Book a free 30-minute call. I will look at your setup and tell you where to start.

Book a call

Get new articles by email

One email when a new article on infrastructure, backend and AI is published. No spam, unsubscribe anytime.