Argo CD has had a rough run of ServerSideDiff-related security advisories this month. The first one, GHSA-3v3m-wc6v-x4x3 / CVE-2026-42880, was critical: a read-only Argo CD user could extract Kubernetes Secret values through the ServerSideDiff endpoint under the right conditions. The follow-up, GHSA-rg3g-4rw9-gqrp / CVE-2026-45737, is rated moderate, but it is the one I find more operationally interesting.
The second advisory is not just “another Argo CD bug.” It is a good example of why Kubernetes security work often fails at the boundaries between tools: Argo CD is trying to show a useful diff, Kubernetes is preserving state for apply semantics, and a harmless-looking annotation can contain the very material everyone assumes is already masked.
If you operate Argo CD, the immediate answer is straightforward: upgrade to a fixed release. But the more durable lesson is broader than a patch version. Diffs are not automatically safe. Read-only access is not automatically low impact. And annotations are not automatically metadata in the “safe to expose” sense.
What changed
The May 1 advisory, CVE-2026-42880, affected Argo CD 3.2.0 through 3.3.8 and was fixed in 3.2.11 and 3.3.9. The issue was in Argo CD’s ServerSideDiff endpoint. According to the advisory, the endpoint could return unmasked Secret data from Kubernetes Server-Side Apply dry-run output. In affected configurations, a user with Argo CD application read permissions could potentially extract Secret values, including service account tokens, TLS private keys, database credentials, and API keys.
The key configuration detail was IncludeMutationWebhook=true in the Argo CD compare-options annotation. That option asks Argo CD to include mutating admission webhook output in the diff. It is a useful feature when admission controllers meaningfully change the object that will exist in the cluster. It is also a reminder that “show me the object Kubernetes would produce” can be a much more sensitive operation than “show me the object in Git.”
The May 13 follow-up advisory, CVE-2026-45737, says the original fix was incomplete. The patched versions listed there are 3.2.12, 3.3.10, and 3.4.2. The remaining exposure path involved Secret values embedded inside the kubectl.kubernetes.io/last-applied-configuration annotation. More specifically: Secret data could still appear in Argo CD server-side diffs when the Secret had previously been created or updated with client-side apply, leaving sensitive values in the last-applied annotation.
That detail matters. A lot of remediation advice stops at “Secrets are base64, not encrypted.” True, but not the point here. The interesting failure mode is that secret material can survive outside the obvious data and stringData fields. If your masking logic only thinks about the top-level Secret fields, it can miss the copy tucked inside an annotation.
Why ServerSideDiff exists in the first place
ServerSideDiff is not a gimmick. Argo CD’s diff strategies documentation describes it as stable since v3.1.0. Instead of only comparing Git manifests to live state locally, Argo CD performs a Kubernetes Server-Side Apply dry-run and compares the predicted live object to the actual live object.
That has real operational value.
Admission controllers can participate in the diff path. Validation problems can surface before sync. Mutation behavior can be visible before it surprises you at deploy time. For teams running Kyverno, Gatekeeper, cloud-provider admission hooks, service mesh injectors, defaulting webhooks, or custom platform controllers, this can make Argo CD’s view of the world closer to the one Kubernetes will actually enforce.
The tradeoff is that the diff path becomes a Kubernetes API interaction with all the complexity that implies. It is no longer just “compare the YAML I rendered to the YAML I see.” It is “ask the API server what this object would look like after apply semantics, defaulting, ownership, and possibly admission behavior.” That predicted object can contain information that was never present in Git.
That is where this class of bug lives.
The annotation problem
The kubectl.kubernetes.io/last-applied-configuration annotation exists to support client-side apply. Kubernetes’ own kubectl documentation describes commands for setting that annotation to match the contents of a file. In practice, client-side apply stores a JSON representation of the last applied manifest on the live object.
For many resource types, that is mostly a source of bloat and occasional confusion. For Secrets, it can become a second copy of sensitive data.
Consider a Secret created with a manifest like this:
apiVersion: v1
kind: Secret
metadata:
name: app-credentials
namespace: payments
type: Opaque
data:
username: cGF5bWVudHM=
password: c3VwZXItc2VjcmV0
If that object is applied using client-side apply, the last-applied annotation may contain a serialized version of the manifest. The values are base64-encoded, but that is not a control. It is just an encoding. Anyone with the value can decode it.
The follow-up Argo CD advisory describes a masking gap around this annotation. The earlier fix masked top-level Secret data in ServerSideDiff responses, but did not fully sanitize Secret data stored inside the last-applied annotation. The root cause described in the advisory is specific: the masking function rewrote the last-applied annotation on one object argument but not the other. In server-side diff mode, the first argument could be the predicted live object, and that predicted object could also carry the annotation.
That is the kind of bug that is easy to miss in review because all the right nouns are present: Secret masking, live state, target state, dry-run, annotation handling. The flaw sits in the exact object flowing through the masking path.
Why read-only Argo CD access deserves more respect
GitOps tools tend to accumulate broad read paths. Operators want to inspect sync status. Developers want to see app health. Platform teams want to delegate visibility without giving everyone deploy rights. That is reasonable, but these advisories show why Argo CD read access should not be treated as harmless.
There are at least four reasons.
First, Argo CD is not only showing Git. It often has a view into rendered manifests, live resources, application events, sync results, and controller decisions. Depending on configuration, it may also expose diffs that are derived from Kubernetes API behavior rather than static source.
Second, the Argo CD application boundary is not always the same as the Kubernetes security boundary. A user who can read an application may see resources from a namespace they could not otherwise inspect directly with kubectl. That is by design in many GitOps setups, but it needs to be an explicit design decision.
Third, platform controllers introduce derived state. Mutation webhooks, defaulting, injectors, and operators can add fields that were not in Git. A diff feature that includes those fields can become a side channel if sanitization is incomplete.
Fourth, legacy operational habits leave residue. The last-applied annotation is a good example. You may have moved to Server-Side Apply or GitOps-managed syncs, but old objects can still carry annotations from earlier workflows. Security posture is often determined by the oldest surviving artifact, not the clean model in the architecture diagram.
What I would check first
If I were reviewing an Argo CD environment after these advisories, I would start with a short, evidence-driven pass rather than a broad panic.
1. Confirm Argo CD versions
The minimum remediation is to run a patched Argo CD version. For CVE-2026-45737, the advisory lists 3.2.12, 3.3.10, and 3.4.2 as patched. For CVE-2026-42880, the earlier patched versions were 3.2.11 and 3.3.9, but the follow-up makes those insufficient for the annotation-specific case.
kubectl -n argocd get deploy,statefulset -o wide
kubectl -n argocd get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'
If Argo CD was installed by Helm, check the chart and app version separately. If it was installed from upstream manifests, compare the applied manifest source to the running images and CRDs. The Argo CD upgrade documentation recommends applying the full manifest set for the target version, not only changing container images, because manifests and parameters can also change between releases.
2. Find applications using ServerSideDiff and mutation webhooks
The high-risk configuration from the first advisory involved IncludeMutationWebhook=true. The follow-up is still centered on ServerSideDiff behavior. I would search Argo CD Applications for compare-options annotations.
kubectl get applications.argoproj.io -A \
-o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{.metadata.annotations.argocd\.argoproj\.io/compare-options}{"\n"}{end}'
That gives you a first pass on where ServerSideDiff is explicitly enabled per application and where mutation webhook output is included. Also check global controller settings if ServerSideDiff was enabled cluster-wide:
kubectl -n argocd get configmap argocd-cmd-params-cm -o yaml | grep -E 'controller.diff.server.side|server.side'
Do not assume the absence of the annotation means the feature is off everywhere. Argo CD supports enabling ServerSideDiff globally.
3. Look for Secrets carrying last-applied annotations
The follow-up advisory depends on sensitive values being present inside kubectl.kubernetes.io/last-applied-configuration. A quick inventory can show how much old client-side apply residue exists.
kubectl get secrets -A -o json \
| jq -r '.items[]
| select(.metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"] != null)
| [.metadata.namespace, .metadata.name, .type] | @tsv'
That command does not print the annotation contents. That is deliberate. The point of the first pass is scope, not spreading secret material into terminals, scrollback, CI logs, or ticket systems.
If you need to inspect whether the annotation contains sensitive fields, do it in a controlled shell with local output handling. Avoid piping raw annotations into shared logs. A safer pattern is to count and classify keys without printing values.
4. Review Argo CD RBAC for read access
After an issue like this, it is tempting to focus only on versions. Patch first, but do not stop there. Review who has applications,get or broad project visibility in Argo CD.
The important question is not “can this user sync?” It is “what application and resource state can this user cause Argo CD to render or return?” That includes UI access, CLI access, API tokens, SSO group mappings, and any default roles that catch authenticated users.
For many teams, the right answer is not to make Argo CD invisible. It is to align Argo CD Projects, RBAC, and namespace ownership so read access follows the same blast-radius model as deployment access.
Reducing blast radius beyond the upgrade
Upgrading Argo CD addresses the known implementation flaw. The rest of the work is about making the next flaw less expensive.
Treat diff output as sensitive
Diffs are operational artifacts, but they can contain secrets, internal hostnames, image names, annotations, admission output, and generated state. Do not casually paste Argo CD diffs into public issues or broad Slack channels. If your incident process collects UI screenshots, treat screenshots of Secret resources and diff views as sensitive by default.
Move secret values out of Git-rendered Secret manifests
If your GitOps repository contains Kubernetes Secret manifests with real values, even base64-encoded values, the diff issue is only one symptom. Prefer a model where Git contains references, templates, encrypted secret material, or external secret definitions rather than plain Kubernetes Secret values.
There are several workable patterns: External Secrets Operator, cloud secret provider integrations, SOPS with age or KMS, Sealed Secrets, or platform-specific secret injection. They have different tradeoffs. The common goal is to avoid normalizing “the full Secret value exists in many places because the deployment pipeline needed it once.”
Clean old last-applied annotations carefully
For Secrets managed by Argo CD or another declarative controller, it may be reasonable to remove stale client-side apply annotations. Do not do that blindly across a production cluster. The annotation can matter for workflows that still use client-side apply.
A cautious cleanup path looks like this:
# inventory only
kubectl get secrets -A -o json \
| jq -r '.items[]
| select(.metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"] != null)
| [.metadata.namespace, .metadata.name] | @tsv'
# targeted removal after owner/workflow validation
kubectl -n <namespace> annotate secret <name> \
kubectl.kubernetes.io/last-applied-configuration-
The trailing hyphen in the annotation command removes the annotation. Validate ownership first. If the Secret is still managed by a process that expects client-side apply state, changing it may affect future apply behavior.
Use Argo CD Projects as security boundaries, not folder labels
Argo CD Projects are one of the main tools for constraining what an application can deploy and where it can deploy it. They also provide a natural place to express tenancy. If every app lives in a broad default project with broad read access, a read-path flaw has a larger blast radius.
Projects should reflect meaningful boundaries: team, environment, sensitivity, or platform ownership. That does not mean every namespace needs a bespoke project. It means the project model should match the access model you would defend during an incident.
Be deliberate with IncludeMutationWebhook
IncludeMutationWebhook=true exists for good reasons. It can make diffs more accurate in environments where mutation webhooks are part of the platform contract. But it should not be enabled casually everywhere because it expands what Argo CD asks Kubernetes to show during diff calculation.
Use it where the operational value is clear. Document why it is enabled. Pair it with narrower Argo CD access and patched versions. If the only reason it is present is that someone copied a sample annotation during troubleshooting, remove it.
The practical takeaway
The easy headline is “upgrade Argo CD.” That is correct, but incomplete.
The more useful lesson is that GitOps controllers sit on several trust boundaries at once. They read Git. They talk to the Kubernetes API. They evaluate live state. They render diffs for humans. They often bridge developer visibility and cluster authority. A bug in any one of those paths can turn a convenience feature into a secret exposure path.
The ServerSideDiff advisories are a clean example because the vulnerable behavior is understandable without hand-waving. Kubernetes dry-run can return predicted live objects. Client-side apply can store prior manifests in annotations. Secret values can exist in more than one field. Argo CD has to mask all of the relevant places before returning diff output. Missing one path is enough.
For platform teams, I would turn this into a small control set:
- Run a fixed Argo CD release: 3.2.12, 3.3.10, 3.4.2, or later in the relevant supported line.
- Inventory ServerSideDiff and
IncludeMutationWebhook=trueusage. - Inventory Secrets with
kubectl.kubernetes.io/last-applied-configurationannotations without dumping their values. - Review Argo CD read access as a meaningful security permission.
- Reduce the number of places real secret values appear, especially in Git-rendered Secret manifests and legacy annotations.
None of that is exotic. It is basic platform hygiene applied to a very specific failure mode. That is usually where the best security work lives: not in declaring a tool unsafe, but in understanding exactly where its trust boundaries are thinner than they look.