Back to skill

Security audit

garmin-pulse

Security checks for vulnerabilities and agentic risk

Overview

This Garmin health sync skill does what it claims, but users should treat the stored Garmin tokens and local health files as sensitive.

Install only if you are comfortable granting a local script and the garminconnect Python dependency access to your Garmin account session and health data. Prefer installing uv through a trusted package manager, consider pinning/reviewing the garminconnect dependency, keep ~/.garminconnect and the health output directory private, and avoid syncing the output into shared folders or backups unless intended.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
README.md:75
Finding

Unverified Remote Installer Downloaded and Executed Directly

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/sync_garmin.py:1
Finding

Automatically Executed Dependency Is Not Pinned to an Audited Version

Content
View full analysis
=3.10" # dependencies = ["garminconnect>=0.2.38"] # /// ``` Related execution behavior is documented in `SKILL.md`, lines 65-67: ```text ## Dependencies The sync script runs via [uv](https://docs.astral.sh/uv/), which reads the inline PEP 723 metadata and installs `garminconnect` automatically — no manual `pip install`. ``` ### Technical Analysis The dependency constraint specifies only a minimum version: ```text garminconnect>=0.2.38 ``` Consequently, future releases satisfying the constraint may be resolved, downloaded, installed, imported, and executed by `uv run`. The repository contains no reviewed lockfile, exact version constraint, or package hash that restricts installation to a specific audited artifact. This is particularly sensitive because the dependency is directly entrusted with the Garmin email, password, MFA callback, and cached OAuth token store through calls such as `Garmin(email, password, ...)` and `client.login(tokenstore)`. A compromised future package release could access this information within the Python process. No evidence was found that the currently named `garminconnect` package is typosquatted or malicious. The vulnerability is the unrestricted acceptance and automatic execution of future compatible releases. ### Attack Path 1. An attacker compromises the upstream package maintainer account, package repository, build pipeline, or an accepted future `garminconnect` release. 2. The malicious release retains a version satisfying `>=0.2.38`. 3. A user invokes the script through `uv run` in an environment that resolves or updates the dependency. 4. uv downloads and installs the malicious release because no exact version or verified hash prevents sel ...[truncated 1005 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 158)May include surrounding context.

md
## Related

- [garmin-nutrition](https://github.com/weirdei/garmin-nutrition) — the write side: add and delete food entries in the Garmin Connect log. This skill reads the day's nutrition totals; that one logs the food. Both share the same `~/.garminconnect/` tokens.

## Credits

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill performs sensitive actions—network access to Garmin, writing health data to disk, and using environment-dependent output paths—yet it declares no explicit tool scope such as allowed-tools or permissions. In an agent ecosystem, missing scope boundaries can let the agent invoke capabilities more broadly than users expect, increasing the risk of unintended network access, local file writes, or reading/writing sensitive health and token-related paths.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 27)May include surrounding context.

md
The password is prompted interactively via `getpass` — it is never echoed to screen, stored in shell history, or passed as a command argument. If Garmin asks for an MFA code, the script prompts for it. On success the user will see `Success! Tokens cached in ~/.garminconnect`. After that, all syncs use cached tokens only — no credentials are needed.

Do not ask the user for their password in chat and do not pass passwords as command-line arguments or via stdin piping, as these methods can expose credentials in process listings or conversation history.

## Syncing Data

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The setup flow requests Garmin credentials and caches authentication tokens locally, but it does not clearly warn users about credential handling, token persistence, or the security implications of storing long-lived session material under the home directory. If those token files are exposed through weak permissions, backups, or malware, an attacker could access the user’s Garmin account data without needing the password again.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/sync_garmin.py (reported line 879)May include surrounding context.

python
def sync_day(client: Garmin, day: date, output_dir: Path) -> None:
    """Sync a single day's data and write the markdown file."""
    day_str = day.isoformat()
    display_date = day.strftime("%B %-d, %Y")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script writes sensitive health information to plaintext markdown files on disk without any warning, access-control checks, or privacy guidance. On multi-user systems, shared backups, synced folders, or compromised endpoints, this can expose highly personal medical and fitness data beyond the user’s intent.

Content

No source excerpt is available for this finding.

External Script Fetching

Low
Category
Supply Chain
Confidence
97% confidence
Finding

The README instructs users to pipe a remotely fetched install script directly into a shell (and similarly use PowerShell iex on Windows), which creates a supply-chain risk: if the hosting site, transport, or script content is compromised, arbitrary code executes immediately on the user's machine. This is especially relevant in a setup guide because users are likely to copy-paste the command without inspection.

Content

Scanner excerpt · README.md (reported line 77)May include surrounding context.

md
- Python 3.10+
- [uv](https://docs.astral.sh/uv/)
  - macOS: `brew install uv`
  - Linux/WSL: `curl -LsSf https://astral.sh/uv/install.sh | sh`
  - Windows: `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"`
- A Garmin Connect account (MFA: see [Troubleshooting](#mfa))

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest lists many Garmin health metrics, but it does not mention respiration or fitness age, while the code retrieves both and persists them in output files. Because the manifest presents a concrete feature list rather than an open-ended summary, these extra synced categories create a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The code includes weekly intensity minutes and lactate threshold/FTP data in the generated markdown, but these metrics are not part of the manifest's stated list of synced data. This extends the implemented behavior beyond the declared description.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest description enumerates the synced data domains and does not mention nutrition/food logs, but the code fetches and writes daily nutrition totals into the markdown output. This is a semantic expansion of the described scope, even though it remains health-related.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.