Feb 26, 2026·7 min read·12 visits
Fleet versions prior to 4.80.1 return unmasked Google Service Account credentials in the global configuration API. Authenticated users, even those with the restricted 'Observer' role, can retrieve the full private key, allowing them to impersonate the service account in Google Cloud.
A deep dive into CVE-2026-27465, where Fleet Device Management inadvertently exposed Google Calendar Service Account private keys to low-privileged users via the application configuration API. This vulnerability highlights the dangers of implicit serialization in Go and the risks of treating configuration data as a 'catch-all' bucket.
Fleet (formerly FleetDM) is the heavy hitter in the open-source device management world. It wraps osquery in a nice enterprise UI, effectively acting as the 'Control Plane' for your entire endpoint infrastructure. When you compromise the control plane, you usually win the game. But today, we aren't looking at a complex RCE or a SQL injection. We are looking at a classic case of 'oversharing'.
In most enterprise RBAC (Role-Based Access Control) models, there is a role usually called 'Observer' or 'Auditor'. The assumption is simple: these users can look, but they can't touch. They are the digital window shoppers of your infrastructure. Security teams often hand these roles out like candy to managers or compliance auditors because, theoretically, they can't break anything.
But what if looking is breaking? CVE-2026-27465 is a logic flaw that turns these passive observers into active threats. By simply asking the Fleet API for the current configuration, an observer gets more than just settings—they get the keys to the kingdom. Specifically, the Google Calendar Service Account private keys used for integrations. It's akin to a bank letting you look at the vault, but also accidentally leaving the combination written on a sticky note on the door.
To understand this bug, you have to understand how Go (Golang) handles JSON serialization. Go is a typed, compiled language that prides itself on simplicity. When you want to turn a struct into JSON, you usually use json.Marshal. This function looks at your struct tags and, unless you explicitly tell it not to, it converts everything it sees into a JSON string.
In Fleet, there is an AppConfig struct. This massive object holds the global configuration for the instance. It includes mundane things like the server URL or the organization name. However, it also includes an Integrations field. Inside that field lived the GoogleCalendar configuration.
When an administrator sets up the Google Calendar integration, they upload a Google Service Account JSON blob. This blob contains the private_key, client_email, and other sensitive metadata. The flaw was fundamentally lazy data modeling. The API endpoint /api/v1/fleet/config was designed to return the application configuration to the frontend so the UI knows how to behave. However, the backend blindly serialized the entire integration map, including the secrets, without masking them first.
This is a textbook CWE-201 (Insertion of Sensitive Information Into Sent Data). The developers likely assumed that since the endpoint requires authentication, it was 'safe enough'. But they forgot the cardinal rule of API design: Frontend clients should only receive data they actually need to render the UI. The frontend needs to know if the integration is active; it does not need the private key to do its job.
Let's look at the fix to understand the breakage. The vulnerability existed because the GoogleCalendar integration was likely just a generic map or struct that got marshaled directly. The fix, introduced in commit 23fc6804, forces the code to acknowledge the sensitivity of the data.
The developers introduced a new type, GoogleCalendarApiKey, with a custom MarshalJSON method. This is the 'Go way' of handling secrets during serialization.
// The new struct wrapper
type GoogleCalendarApiKey struct {
Values map[string]string
masked bool
}
// Custom Marshaler - The Gatekeeper
func (k GoogleCalendarApiKey) MarshalJSON() ([]byte, error) {
// If the masked flag is true, return stars instead of the key
if k.masked {
return json.Marshal("********")
}
// Otherwise, return the raw values (for internal use)
return json.Marshal(k.Values)
}They also updated the AppConfig.Obfuscate() method. This method is called specifically before sending data out to the API response writer. It iterates through the integrations and flips the masked bit to true.
// server/fleet/integrations.go
for _, gcIntegration := range c.Integrations.GoogleCalendar {
// This sets the internal bool, triggering the "********" return above
gcIntegration.ApiKey.SetMasked()
}Before this patch, the Obfuscate method simply didn't touch the Google Calendar integration, so json.Marshal did what it was told: it dumped the raw private key in cleartext.
Exploiting this is trivially easy if you have a foothold. You don't need memory corruption; you just need curl and a valid session token.
The Attack Chain:
private_key.Here is a visual representation of the flow:
Nuclei Detection Template:
If you are hunting for this in your own environment (internally, of course), the detection logic is simple. You authenticate, hit the endpoint, and look for the specific private key header.
id: fleet-google-calendar-leak
requests:
- method: GET
path:
- "{{BaseURL}}/api/v1/fleet/config"
matchers:
- type: regex
part: body
regex:
- "-----BEGIN PRIVATE KEY-----"Once the attacker has the key, they are no longer bound by Fleet's RBAC. They are now an authenticated Google Cloud Service Account. Depending on the IAM permissions granted to that service account (often overly permissive "Editor" roles), they could access calendars, drive files, or even spin up resources.
Why is this a big deal? Because Service Account keys don't rotate automatically. Unlike a user session cookie that expires in 24 hours, a Google Service Account key is valid until someone explicitly deletes it.
An attacker who dumps this config today can come back in six months and use that key, provided the administrator hasn't rotated it. The access allows the attacker to bypass Fleet entirely and interact directly with Google APIs.
Furthermore, because this integration is for Google Calendar, the immediate impact often involves corporate espionage. Attackers can read meeting invites, see who is meeting with whom (M&A activity?), and find Zoom links. In a worst-case scenario where the Service Account was created with Owner privileges on the GCP project (a common mistake by lazy sysadmins), this is a full cloud environment compromise.
If you are running Fleet, you need to upgrade to v4.80.1 immediately. This stops the bleeding by ensuring the API no longer returns the key.
HOWEVER—and this is critical—patching the code does not fix the breach.
If you ever ran a vulnerable version with a Google Calendar integration configured, you must assume that key is compromised. The patch only hides the key from future requests. It does not invalidate the key that was already exposed.
Remediation Steps:
Do not skip the rotation step. If you do, you are locking the front door while the burglar is already sitting on your couch.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Fleet Fleet Device Management | < 4.80.1 | 4.80.1 |
| Attribute | Detail |
|---|---|
| CWE | CWE-201 (Insertion of Sensitive Information Into Sent Data) |
| CVSS v3.1 | 4.3 (Medium) |
| Attack Vector | Network (Authenticated) |
| Privileges Required | Low (Observer) |
| Impact | Information Disclosure (High Confidentiality) |
| Fixed Version | 4.80.1 |
Insertion of Sensitive Information Into Sent Data