Servarat Blog
  • Get Support
ArgoCD vulnerability

ArgoCD ServerSideDiff Secret Extraction: Read-Only Users Can Pull Plaintext Credentials from Kubernetes

Mohamed Magdy

Mohamed Magdy

19 May 2026 — 6 min read
Share
ArgoCD ServerSideDiff Secret Extraction: Read-Only Users Can Pull Plaintext Credentials from Kubernetes
Argo CD is a declarative, GitOps continuous delivery tool for 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-controller is 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-token volume 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:

  1. You're running ArgoCD 3.2.0–3.3.8
  2. You have Applications that manage Kubernetes Secrets
  3. (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:

  1. Lists all managed Secrets in an ArgoCD Application
  2. For each Secret, calls ServerSideDiff with a minimal target manifest (no data fields)
  3. Parses the raw gRPC response from the SSA dry-run
  4. Extracts any plaintext Secret data from the targetState field
  5. Decodes base64 values and prints them

Running the PoC requires:

  • Network access to the ArgoCD API server
  • A valid token with get permissions 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 ServerSideDiff for 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

  1. Do I have read-only or guest users in my ArgoCD instance? If yes, they're at risk.
  2. Do I manage Secrets in Kubernetes via ArgoCD? If yes, those Secrets are extractable.
  3. Do I have audit logs for ArgoCD API calls? If no, you can't tell if exploitation happened.
  4. Have I rotated the credentials in my Secrets recently? If not, assume they're compromised.
  5. 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:

  1. 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
  1. 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}')
  1. Review ArgoCD RBAC: Remove access for users who shouldn't have it.
  2. Enable audit logging: Set up proper logging for all API calls.
  3. 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

Read more

The Hidden Security Risks of Self-Hosting Everything

The Hidden Security Risks of Self-Hosting Everything

Featured image alt text: A few years back, self-hosting was a niche thing. You'd run Nextcloud on an old laptop, brag about it on a forum, and move on. Now it's practically a movement. Every other thread is someone pulling their photos off Google, their notes

By Mohamed Magdy 31 May 2026
Anthropic Will Open Up Its Mythos-Class Models to Everyone

Anthropic Will Open Up Its Mythos-Class Models to Everyone

Anthropic says it's clearing the way for a public release of its Mythos-class models, months after holding them back over security concerns. The company had originally limited access because of what it saw as real risk to software in the wild, both public and private. Why Mythos stayed

By Mohamed Magdy 29 May 2026
Kill Your SSH Keys: Production SSH Certificate Authentication with step-ca

Kill Your SSH Keys: Production SSH Certificate Authentication with step-ca

Every infrastructure team I've worked with has the same dirty secret. There's a directory somewhere — usually on a shared drive, sometimes in a 1Password vault if the team is feeling responsible — that contains the SSH public keys of everyone who's ever needed access to

By Mohamed Magdy 28 May 2026
Bash History Security: 5 Ways to Keep Passwords and API Keys Out of Plain Text

Bash History Security: 5 Ways to Keep Passwords and API Keys Out of Plain Text

You've Done It: Pasted a Password, Hit Enter, Immediately Panicked You paste a curl command with an API key embedded. You hit Enter. The command works. Then you realize: where did that string just land? Bash saved it. To ~/.bash_history. Which is readable by anyone with access

By Mohamed Magdy 26 May 2026
Servarat Blog
Powered by Ghost