chore(deps): update dependency garminconnect to v0.3.5 [security]#262
Open
renovate[bot] wants to merge 1 commit into
Open
chore(deps): update dependency garminconnect to v0.3.5 [security]#262renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
0.3.3→0.3.5garminconnect Has Insecure Permission Assignment for Garmin OAuth Token Store
CVE-2026-54447 / GHSA-wjhr-76vg-2hvc
More information
Details
Insecure Permission Assignment for Garmin OAuth Token Store
Summary
garminconnect(≤ 0.3.4) wrote its OAuth token store to disk without restricting file-system permissions. Under the default Linux umask (022) the token filegarmin_tokens.jsonwas created world-readable (0o644). The file contains the DI refresh token, so any other local user on a shared host could read it and obtain persistent, unauthorized access to the victim's Garmin Connect account.<= 0.3.40.3.5Details
Client.dump()created the token directory and file with nomodeargument, leaving permissions entirely to the process umask:The serialized payload includes
di_token,di_refresh_token, anddi_client_id. The call is in the core library (Garmin.login(tokenstore=...)persists tokens this way), and all shipped usage examples default the token store to~/.garminconnect.Under
umask 022the resulting permissions were:0o755garmin_tokens.json→0o644(world-readable)A separate, unprivileged user on the same machine could read the file with a plain
open()— no elevated privileges required — and extract the refresh token.Impact
Local credential theft / privilege escalation on multi-user Linux or macOS hosts running under a permissive umask. The stolen refresh token can be exchanged for fresh access tokens via Garmin's OAuth endpoint, granting ongoing access to the victim's account (health/fitness data, activity history, device management) until the token is revoked.
Patch
Fixed in 0.3.5 (commit
77a3837).dump()now creates the directory as0o700and writes the token file as0o600regardless of umask — usingos.open(..., O_CREAT|O_WRONLY|O_TRUNC, 0o600)withO_NOFOLLOWwhere available, plus a defensivechmodthat also tightens a pre-existing loose file:Verified under
umask 022: directory0o700, file0o600, no group/other access.Workarounds
If you cannot upgrade immediately, restrict the token store manually and keep it owner-only:
Remediation
garminconnect >= 0.3.5:on the next write, so an existing world-readable file stays exposed until
then:
compromised. Re-authenticate (delete the token store and log in again) so
a new token is issued; consider the previously stored token potentially
read by others until rotated.
Credit
Reported by EQSTLab via a private security advisory. garminconnect thanks them for the detailed, responsible disclosure.
Severity
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
cyberjunky/python-garminconnect (garminconnect)
v0.3.5Compare Source
What's Changed
This release includes a security fix (please upgrade), major login
resilience improvements, two new activity-editing methods, and bug fixes.
🔒 Security
Client.dump()previously wrote
garmin_tokens.jsonunder the process umask, leaving itworld-readable (
0o644) on the default Linux umask. The file holds the DIrefresh token, so any local user on a shared host could read it and gain
persistent access to the account. Tokens are now written
0o600inside a0o700directory (withO_NOFOLLOWand a defensivechmod), regardless ofumask. Affected: ≤ 0.3.4. Patched: 0.3.5. Upgrading is strongly advised
for anyone storing tokens on a multi-user system.
✨ Login resilience (fixes #369)
against the API before the chain accepts it. A token the API rejects
(401/403 — an account/region-specific condition) is discarded and the next
strategy is tried automatically. Only definitive auth rejections fall
through; transient 5xx/network errors never block a working login.
API rejects them, they're discarded and a fresh credential login runs
automatically — fixing the long-standing footgun where a stale token cache
silently short-circuited the login chain on every run.
logout()is now functional (was a deprecated no-op): clears in-memoryauth state and removes cached tokens on disk. Callable as
g.logout()org.logout(tokenstore).verify_loginoption (Garmin(..., verify_login=False)) to restorethe legacy "first token wins" behavior.
skip_strategieson the client to force or skip specific loginstrategies — useful for diagnosing which auth path works on your account.
child/family-account detection,
429errors preserved through the loginwrapper, and no more stack-trace dumps for expected login failures.
🆕 New API methods
set_activity_description(activity_id, description)(#367)set_activity_exercise_sets(activity_id, payload)(#368)Both new methods are available in the interactive demo under the new
✏️ Activity Editing menu.
🐛 Fixes
get_training_readinessnow correctly annotatedlist[dict[str, Any]]— theendpoint returns a list of snapshots, not a single dict (#361). Downstream
tooling that validates against the return type (e.g. pydantic-based MCP
servers) no longer breaks.
get_morning_training_readinesskeeps defensive handling for a single-dictresponse.
typed.get_training_readiness()normalizes an empty responseto
[].🧪 Internal / tooling
network required.
test_strategy.py— interactive diagnostic to run each login strategy inisolation and tee output to a log.
Upgrade
Full Changelog: cyberjunky/python-garminconnect@0.3.4...0.3.5
v0.3.4Compare Source
What's Changed
Features
Typed response layer (
g.typedaccessor) — an optional Pydantic namespace that wraps high-value read endpoints with validated, IDE-friendly models. Requirespydantic>=2.0; zero impact on existing users.Supported methods in this release:
get_stats,get_user_summaryDailyStatsget_sleep_dataSleepDataget_hrv_dataHrvData | Noneget_body_batterylist[BodyBatteryEntry]get_training_readinesslist[TrainingReadiness]get_activities_by_datelist[Activity]Validation failures raise
GarminConnectResponseValidationErrorwith.rawpreserved. Extra/missing fields are tolerated so schema drift doesn't break running apps. Marked experimental — the surface may evolve based on feedback.Bug Fixes
diauth.garmin.cnforis_cn=Trueaccounts. Previously the.comendpoint was always used, causing refresh failures (invalid_grant) after the ~4-hour access token TTL expired. Cron-driven sync jobs and long-running sessions on CN accounts are now fixed. (PR #360 by @MidnightV1)Contributors
Full Changelog: cyberjunky/python-garminconnect@0.3.3...0.3.4
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.