Insecure Permission Assignment for Garmin OAuth Token Store
The `garminconnect` Python library versions 0.3.4 and earlier insecurely assigned world-readable file permissions to the `garmin_tokens.json` OAuth token store, allowing local attackers on multi-user systems to steal refresh tokens and gain persistent, unauthorized access to victims' Garmin Connect accounts.
The garminconnect Python library, specifically versions 0.3.4 and older, contained a critical vulnerability (CVE-2026-54447) related to insecure file permission assignment. When the library created or updated the garmin_tokens.json file, which stores sensitive Garmin OAuth refresh tokens, it failed to explicitly set restrictive file permissions. Under common Linux and macOS umask settings, this resulted in the garmin_tokens.json file being world-readable (0o644). This flaw allowed any unprivileged local user on a multi-user system to read the file, extract the refresh token, and consequently gain persistent, unauthorized access to the victim's Garmin Connect account. This issue was identified and reported by EQSTLab. The vulnerability impacts users who have configured the garminconnect library to interact with their Garmin accounts on shared host environments.
Attack Chain
- A user installs and configures the
garminconnectlibrary version 0.3.4 or earlier on a multi-user Linux or macOS system and authenticates with their Garmin Connect account. - The
garminconnectlibrary callsClient.dump(), persisting OAuth tokens, including thedi_refresh_tokenanddi_client_id, to the file~/.garminconnect/garmin_tokens.json. - Due to the
Client.dump()function not specifying amodeargument for file creation, and the system operating under a default umask (e.g.,022), thegarmin_tokens.jsonfile is created with world-readable permissions (0o644). - A separate, unprivileged local user on the same shared system discovers and reads the
~/.garminconnect/garmin_tokens.jsonfile, which contains the sensitive OAuth refresh token. - The attacker extracts the
di_refresh_tokenanddi_client_idfrom the world-readable JSON file. - The attacker then uses the stolen
di_refresh_tokenanddi_client_idto exchange for fresh access tokens from Garmin's OAuth endpoint, bypassing the need for re-authentication. - The attacker uses these valid access tokens to gain unauthorized, persistent access to the victim's Garmin Connect account, enabling access to health data, activity history, and device management features.
Impact
Successful exploitation of CVE-2026-54447 leads to local credential theft and unauthorized access to victims' Garmin Connect accounts. On multi-user Linux or macOS hosts, any local, unprivileged user can read the garmin_tokens.json file, which contains the refresh token. This allows the attacker to obtain persistent access to the victim's Garmin Connect data, including sensitive health and fitness information, activity history, and device management capabilities, until the stolen refresh token is explicitly revoked. The primary risk is unauthorized access to personal data and potential account hijacking.
Recommendation
- Upgrade garminconnect: Immediately upgrade the
garminconnectlibrary to version 0.3.5 or later to mitigate CVE-2026-54447 by runningpip install --upgrade garminconnect. - Remediate existing files: Manually set secure permissions for the token store by executing
chmod 700 ~/.garminconnectandchmod 600 ~/.garminconnect/garmin_tokens.jsonto protect existinggarmin_tokens.jsonfiles. - Rotate compromised tokens: If the
garmin_tokens.jsonfile was exposed on a shared host, treat the refresh token as compromised. Delete the existing token store (e.g.,rm ~/.garminconnect/garmin_tokens.json) and log in again using thegarminconnectlibrary to mint a fresh, securely stored token.