ArgoCD ServerSideDiff Secret Extraction: Read-Only Users Can Pull Plaintext Credentials from Kubernetes
The Vulnerability: What Happened
ArgoCD has a feature called ServerSideDiff that compares what's currently deployed in Kubernetes with what you're trying to deploy. It's supposed to show you the diff before you apply changes—super useful. The problem is straightforward: this endpoint returns unmasked Kubernetes Secret data to users who only have read-only access.
Think about it this way: if I have read-only access to your ArgoCD instance, I can call the ServerSideDiff endpoint and get back real Secret values including:
- Service account tokens
- Database passwords
- API keys
- TLS certificates
- SSH keys
- Docker registry credentials
Any secret stored in Kubernetes that's managed by an ArgoCD Application becomes extractable.
Why This Slipped Through
ArgoCD has a masking system called hideSecretData() that's used in other endpoints to scrub Secret values before returning them to users. The team implemented this in GetManifests, GetResource, PatchResource, and others. But they missed ServerSideDiff.
When the ServerSideDiff endpoint constructs its response, it builds it directly from the raw Kubernetes Server-Side Apply (SSA) dry-run output without applying the masking:
responseDiffs = append(responseDiffs, &v1alpha1.ResourceDiff{
TargetState: string(diffRes.PredictedLive), // raw, unmasked
LiveState: string(diffRes.NormalizedLive), // raw, unmasked
})
There is a defense layer called removeWebhookMutation() that normally strips non-ArgoCD-managed fields from the SSA response before it reaches the API. This prevents real Secret values from leaking through. But this defense gets completely skipped if the Application has the annotation:
argocd.argoproj.io/compare-options: IncludeMutationWebhook=true
When IncludeMutationWebhook=true, the code skips the removeWebhookMutation() call entirely. The raw Kubernetes response with real Secret values flows directly into the API response with zero masking.
The kicker: This annotation is explicitly documented in ArgoCD as a way to "include mutations by other field managers." Legitimate use case. But it opens the door to extraction.
Why It Works: The Technical Detail
When ServerSideDiff runs, it calls kubectl apply --server-side --dry-run=server. The Kubernetes API server reads Secret data from etcd and includes it in the response.
For extraction to work, the Secret's data fields must be "owned" by at least one field manager other than argocd-controller. Here's why:
- If
argocd-controlleris the only field manager, the SSA dry-run garbage-collects the data fields (because your target manifest doesn't include them). No leak. - If a second manager exists (like
kube-controller-manager,kubelet, or a webhook), that manager retains ownership. The real Secret values survive in the response.
In production, it's common for multiple managers to own parts of a Kubernetes resource. For example:
- Kubelet owns the
service-account-tokenvolume mount - A webhook might own cert annotations
- ArgoCD owns deployment strategy
Any of these scenarios mean the Secret data persists in the SSA response when IncludeMutationWebhook=true.
Who Can Exploit This
Default case: Any authenticated ArgoCD user.
The default RBAC policy gives every authenticated user get access to Applications via the catch-all policy. They can call ServerSideDiff without additional permissions.
With IncludeMutationWebhook=true: The exploitation is even easier—no special Application permissions needed.
⚠️ This applies to every managed Secret in your cluster. If ArgoCD syncs it, it's extractable.
How to Check If You're Vulnerable
You're affected if:
- You're running ArgoCD 3.2.0–3.3.8
- You have Applications that manage Kubernetes Secrets
- (Optionally) Any Application has the annotation
compare-options: IncludeMutationWebhook=true
Check your Applications:
# List all applications with IncludeMutationWebhook annotation
kubectl get applications -A -o jsonpath='{range .items[?(@.metadata.annotations.argocd\.argoproj\.io/compare-options)]}
{.metadata.namespace}/{.metadata.name}: {.metadata.annotations.argocd\.argoproj\.io/compare-options}{"\n"}{end}'
If any output contains IncludeMutationWebhook=true, you have an explicit exposure vector.
Proof of Concept (Understanding the Attack)
The PoC is provided in the advisory as a Python script that:
- Lists all managed Secrets in an ArgoCD Application
- For each Secret, calls
ServerSideDiffwith a minimal target manifest (no data fields) - Parses the raw gRPC response from the SSA dry-run
- Extracts any plaintext Secret data from the
targetStatefield - Decodes base64 values and prints them
Running the PoC requires:
- Network access to the ArgoCD API server
- A valid token with
getpermissions on the Application - The Secret must have at least one field manager other than
argocd-controller
Result: Plaintext extraction of all Secret values.
⚠️ Don't run the PoC against production unless you own it. This is a destructive knowledge check, not a testing tool.
Impact Assessment
Severity: Critical (CVSS 9.3).
Real-world risk:
- Service account tokens → full cluster access
- Database credentials → data exfiltration
- TLS certificates → man-in-the-middle attacks on your services
- API keys → compromise of downstream systems (cloud providers, SaaS platforms)
- Docker registry credentials → poisoned container images
Who's at risk:
- Any organization using ArgoCD to manage Kubernetes
- Any organization where non-admin users have ArgoCD access (read-only deployments, shared clusters, multi-tenant setups)
Patching
Upgrade Immediately
ArgoCD 3.3.x users:
# Upgrade to 3.3.9 or later
helm upgrade argocd argo/argo-cd --version 3.3.9
# or
kubectl set image -n argocd deploy/argocd-server argocd-server=quay.io/argoproj/argocd:v3.3.9
ArgoCD 3.2.x users:
# Upgrade to 3.2.11 or later
helm upgrade argocd argo/argo-cd --version 3.2.11
ArgoCD via Operator:
kubectl patch argocdconfig argocd -p '{"spec":{"version":"v3.3.9"}}' --type=merge
Verify the patch:
kubectl get pods -n argocd -o jsonpath='{.items[0].spec.containers[0].image}'
# Should show v3.3.9 or v3.2.11 or later
What the Patch Does
The patch adds hideSecretData() masking to the ServerSideDiff response, same as the other endpoints. It also improves the removeWebhookMutation() logic to handle edge cases where the defense was skipped.
Detection: How to Know If You Were Exploited
Check your ArgoCD API server logs for patterns:
# Look for ServerSideDiff calls from unexpected users
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server --tail=1000 | grep -i "serversidediff\|resourcediff"
# Check for rapid successive calls to the same Application (sign of extraction attempt)
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server --tail=1000 | grep -E "ApplicationService|ServerSideDiff" | head -20
Look for:
- Calls from read-only users or service accounts you don't recognize
- Rapid sequential calls to
ServerSideDifffor multiple Secrets - Calls from outside your normal ArgoCD usage patterns
If you have no API audit logs, enable them now:
# Enable audit logging in ArgoCD
kubectl patch cm argocd-cmd-params-cm -p '{"data":{"application.loglevel":"debug"}}'
kubectl rollout restart deployment/argocd-server -n argocd
Temporary Mitigation (Until You Can Patch)
Option 1: RBAC Lockdown (immediate, recommended)
Restrict who can call ServerSideDiff:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-reader-no-diff
rules:
- apiGroups: ["argoproj.io"]
resources: ["applications"]
verbs: ["get", "list", "watch"]
- apiGroups: ["argoproj.io"]
resources: ["applications/resources"]
verbs: ["get"]
# Note: NOT including applications/diff or applications/manifests
Apply this to all non-admin users. This prevents calling ServerSideDiff but still allows viewing Application status.
Option 2: Network Segmentation (temporary, not a fix)
If you can isolate ArgoCD API access to trusted networks/IPs only, do it. Use Kubernetes Network Policies or firewall rules to restrict who can reach the ArgoCD API server.
Option 3: Remove IncludeMutationWebhook Annotations (quick win)
If you've explicitly set compare-options: IncludeMutationWebhook=true on Applications, remove it:
# Remove the annotation from all applications
kubectl patch applications -A -p '{"metadata":{"annotations":{"argocd.argoproj.io/compare-options":null}}}' --overwrite --type=merge
⚠️ This may change diff behavior for affected Applications. Test in staging first.
None of these are substitutes for patching. Patch as soon as possible.
Questions to Ask Yourself
- Do I have read-only or guest users in my ArgoCD instance? If yes, they're at risk.
- Do I manage Secrets in Kubernetes via ArgoCD? If yes, those Secrets are extractable.
- Do I have audit logs for ArgoCD API calls? If no, you can't tell if exploitation happened.
- Have I rotated the credentials in my Secrets recently? If not, assume they're compromised.
- Is my ArgoCD version 3.2.0–3.3.8? Check with
argocd version.
Post-Patch Checklist
After upgrading to 3.3.9/3.2.11:
- Rotate all Secrets managed by ArgoCD:
# For each Secret, delete and re-create or update the values
kubectl delete secret <secret-name> -n <namespace>
# Then re-sync the Application in ArgoCD to re-create it
- Rotate service account tokens:
# Delete and re-create the token secret for each service account
kubectl delete secret $(kubectl get secrets -A | grep "default-token" | awk '{print $2}') -n $(kubectl get secrets -A | grep "default-token" | awk '{print $1}')
- Review ArgoCD RBAC: Remove access for users who shouldn't have it.
- Enable audit logging: Set up proper logging for all API calls.
- Verify no IncludeMutationWebhook annotations exist (unless explicitly needed).
Why This Matters More Than You Think
This isn't an exotic attack. It's a straightforward "I have read-only access, let me extract everything" vulnerability. In a shared or multi-tenant Kubernetes environment, this is particularly dangerous because:
- Developers often get read-only ArgoCD access to see what's deployed
- Platform teams use ArgoCD to manage cluster-wide infrastructure
- Secrets stored in Kubernetes are usually high-value (database creds, API keys, tokens)
Once an attacker has the tokens or credentials, they can:
- Access your databases
- Impersonate service accounts
- Call cloud provider APIs with your credentials
- Compromise downstream systems that depend on those credentials
References
- GHSA-3v3m-wc6v-x4x3 (ArgoCD GitHub Security Advisory)
- CVE details (pending CVE assignment)
- ArgoCD official security advisory
- Kubernetes Server-Side Apply documentation
- RBAC for ArgoCD best practices