CVE-2026-6720
Description
When calicoctl is invoked with --log-level=info or --log-level=debug, the client prints the full contents of its loaded connection-configuration struct to stderr in a single log line. The struct embeds every credential calicoctl uses to talk to the cluster — inline kubeconfig (with bearer token), Kubernetes API bearer token, etcd password, and inline PEM-encoded etcd client certificate and key. Any reader of that stderr stream — CI job logs, session-recording archives, shared support-ticket transcripts, or local filesystem viewers on the host that ran calicoctl — can extract these credentials with zero Kubernetes privilege. calicoctl's default log level is panic, so this issue only triggers when verbose logging is explicitly enabled.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
calicoctl logs full connection credentials (kubeconfig, tokens, etcd secrets) to stderr when --log-level is info or debug, enabling credential extraction by any stderr reader.
Vulnerability
When calicoctl is invoked with --log-level=info or --log-level=debug, it prints the complete contents of its connection-configuration struct to stderr in a single log line. This struct embeds the Kubernetes API bearer token, inline kubeconfig (with bearer token), etcd password, inline PEM-encoded etcd client certificate and key, and (as noted in the fix comment) the etcd CA certificate. The default log level is panic, so this only occurs when verbose logging is explicitly enabled. Affected versions are all calicoctl releases prior to the fix, which was implemented in pull request #12535 [1] and backported to release 3.32 (#12536 [2]) and 3.31 (#12537 [3]).
Exploitation
An attacker does not need any Kubernetes cluster privileges. The only requirement is access to the stderr output where the credential line appears. Common channels include CI job logs, session-recording archives, shared support-ticket transcripts, or the local filesystem of the host running calicoctl. The attacker simply reads the log line containing the sensitive struct dump.
Impact
Successful exploitation results in full disclosure of all credentials used by calicoctl to communicate with the cluster. This includes Kubernetes bearer tokens, etcd credentials, and TLS client certificates, which can be used to authenticate to the API server and etcd with the same privileges as the original calicoctl session, potentially leading to complete cluster compromise.
Mitigation
The vulnerability is fixed in the main branch via pull request #12535 [1] (merged April 2026). Backports are available for release 3.32 (#12536 [2]) and release 3.31 (#12537 [3]). Users should upgrade to a patched version immediately. If upgrading is not possible, avoid using --log-level=info or --log-level=debug; keeping the default log level (panic) prevents the leak.
- Sanitize calicoctl log output by Behnam-Shobiri · Pull Request #12535 · projectcalico/calico
- [release 3.32] Remove sensitive info from logs (pick 12535) by Behnam-Shobiri · Pull Request #12536 · projectcalico/calico
- [release 3.31] Remove sensitive info from logs (pick 12535) by Behnam-Shobiri · Pull Request #12537 · projectcalico/calico
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
3ce3ab2b39b58Remove sensitive info from logs (#12537)
1 file changed · +14 −1
calicoctl/calicoctl/commands/clientmgr/client.go+14 −1 modified@@ -37,7 +37,20 @@ func NewClient(cf string) (client.Interface, error) { log.Info("Error loading config") return nil, err } - log.Infof("Loaded client config: %#v", cfg.Spec) + // Do not log cfg.Spec directly (e.g. %#v / %+v / %v). + // It embeds K8sAPIToken, KubeconfigInline, EtcdPassword, EtcdKey, and EtcdCert. + log.WithFields(log.Fields{ + "datastoreType": cfg.Spec.DatastoreType, + "etcdEndpoints": cfg.Spec.EtcdEndpoints, + "k8sAPIEndpoint": cfg.Spec.K8sAPIEndpoint, + "kubeconfig": cfg.Spec.Kubeconfig, + "kubeconfigInlineSet": cfg.Spec.KubeconfigInline != "", + "etcdUsernameSet": cfg.Spec.EtcdUsername != "", + "etcdPasswordSet": cfg.Spec.EtcdPassword != "", + "etcdKeyInlineSet": cfg.Spec.EtcdKey != "", + "etcdCertInlineSet": cfg.Spec.EtcdCert != "", + "k8sAPITokenSet": cfg.Spec.K8sAPIToken != "", + }).Info("Loaded client config") return NewClientFromConfig(cfg) }
8d87eddcb5ebRemove sensitive info from logs (#12536)
1 file changed · +14 −1
calicoctl/calicoctl/commands/clientmgr/client.go+14 −1 modified@@ -37,7 +37,20 @@ func NewClient(cf string) (client.Interface, error) { log.Info("Error loading config") return nil, err } - log.Infof("Loaded client config: %#v", cfg.Spec) + // Do not log cfg.Spec directly (e.g. %#v / %+v / %v). + // It embeds K8sAPIToken, KubeconfigInline, EtcdPassword, EtcdKey, and EtcdCert. + log.WithFields(log.Fields{ + "datastoreType": cfg.Spec.DatastoreType, + "etcdEndpoints": cfg.Spec.EtcdEndpoints, + "k8sAPIEndpoint": cfg.Spec.K8sAPIEndpoint, + "kubeconfig": cfg.Spec.Kubeconfig, + "kubeconfigInlineSet": cfg.Spec.KubeconfigInline != "", + "etcdUsernameSet": cfg.Spec.EtcdUsername != "", + "etcdPasswordSet": cfg.Spec.EtcdPassword != "", + "etcdKeyInlineSet": cfg.Spec.EtcdKey != "", + "etcdCertInlineSet": cfg.Spec.EtcdCert != "", + "k8sAPITokenSet": cfg.Spec.K8sAPIToken != "", + }).Info("Loaded client config") return NewClientFromConfig(cfg) }
12908bb48a5bRemove sensitive info from logs (#12535)
1 file changed · +14 −1
calicoctl/calicoctl/commands/clientmgr/client.go+14 −1 modified@@ -37,7 +37,20 @@ func NewClient(cf string) (client.Interface, error) { log.Info("Error loading config") return nil, err } - log.Infof("Loaded client config: %#v", cfg.Spec) + // Do not log cfg.Spec directly (e.g. %#v / %+v / %v). + // It embeds K8sAPIToken, KubeconfigInline, EtcdPassword, EtcdKey, and EtcdCert. + log.WithFields(log.Fields{ + "datastoreType": cfg.Spec.DatastoreType, + "etcdEndpoints": cfg.Spec.EtcdEndpoints, + "k8sAPIEndpoint": cfg.Spec.K8sAPIEndpoint, + "kubeconfig": cfg.Spec.Kubeconfig, + "kubeconfigInlineSet": cfg.Spec.KubeconfigInline != "", + "etcdUsernameSet": cfg.Spec.EtcdUsername != "", + "etcdPasswordSet": cfg.Spec.EtcdPassword != "", + "etcdKeyInlineSet": cfg.Spec.EtcdKey != "", + "etcdCertInlineSet": cfg.Spec.EtcdCert != "", + "k8sAPITokenSet": cfg.Spec.K8sAPIToken != "", + }).Info("Loaded client config") return NewClientFromConfig(cfg) }
Vulnerability mechanics
Root cause
"The calicoctl client logs the entire connection-configuration struct (cfg.Spec) using a Go format verb (%#v) that dumps all fields, including embedded credentials, to stderr."
Attack vector
An attacker who can read the stderr output of a calicoctl process — for example, through CI job logs, session-recording archives, shared support-ticket transcripts, or local filesystem viewers on the host — can extract credentials. The trigger is simply invoking calicoctl with --log-level=info or --log-level=debug, which causes the client to log the full cfg.Spec struct via log.Infof("Loaded client config: %#v", cfg.Spec) [patch_id=2961906]. No Kubernetes API access is needed; the credentials (K8sAPIToken, KubeconfigInline, EtcdPassword, EtcdKey, EtcdCert) are printed in plaintext to stderr.
Affected code
The vulnerable code is in calicoctl/calicoctl/commands/clientmgr/client.go, in the NewClient function. The line log.Infof("Loaded client config: %#v", cfg.Spec) prints the entire cfg.Spec struct, which embeds K8sAPIToken, KubeconfigInline, EtcdPassword, EtcdKey, and EtcdCert [patch_id=2961906].
What the fix does
The patch replaces the single log.Infof("Loaded client config: %#v", cfg.Spec) call with a structured log.WithFields(...).Info("Loaded client config") that explicitly lists only non-sensitive fields (datastoreType, etcdEndpoints, k8sAPIEndpoint, kubeconfig) and boolean "Set" flags for each credential field (e.g. k8sAPITokenSet: true) instead of the credential values themselves [patch_id=2961906]. This closes the vulnerability by ensuring that inline bearer tokens, passwords, and PEM-encoded certificates are never serialized into log output.
Preconditions
- configcalicoctl must be invoked with --log-level=info or --log-level=debug (default is panic, so verbose logging must be explicitly enabled)
- inputAn attacker must have read access to the stderr stream where calicoctl logs (e.g., CI logs, terminal scrollback, session recordings, support tickets)
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.