Back to skill

Security audit

garmin-connect-health

Security checks for vulnerabilities and agentic risk

Overview

This Garmin skill appears to do what it claims, but it handles account credentials and persists sensitive health data locally with safeguards that users should review before installing.

Install only if you are comfortable giving the skill access to your Garmin account and storing detailed health data on this machine. Prefer macOS Keychain over CLI passwords or ~/.garmin_credentials, keep the data and token directories private, avoid shared/synced locations for GARMIN_DATA_DIR and GARMIN_TOKENSTORE, and consider pinning the garminconnect dependency before use.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Warning
Location
garmin_health.py:58
Finding

Sensitive health records are stored without restrictive filesystem permissions

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
garmin_health.py:263
Finding

Plaintext credential file is used despite unsafe permissions

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
garmin_health.py:789
Finding

Garmin account passwords can be supplied through exposed command-line arguments

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
skill.json:53
Finding

Security-sensitive third-party dependency is not pinned to a reviewed version

Content
View full analysis
=3.10", "pip": [ "garminconnect>=0.3.11" ] } ``` The installation instructions use the same open-ended constraint: ```bash pip install "garminconnect>=0.3.11" ``` ### Technical Analysis The minimum-version constraint permits installation of any later `garminconnect` release available from the configured package index. It does not ensure that users install the same dependency version that was reviewed alongside this Skill. This is security-sensitive because `garminconnect` receives the Garmin email address and password, manages OAuth tokens, and processes complete health records. A compromised future release, package-index compromise, or malicious resolution through an untrusted index could execute with the Skill user's privileges and access those secrets. The audited project does not contain evidence that the current named package is malicious. The risk arises from allowing unreviewed future versions without exact pins, hashes, or a lock file. ### Attack Path 1. A future allowed release of `garminconnect` is compromised, malicious, or unexpectedly modified, or the user resolves dependencies through an untrusted package index. 2. The user follows the documented `pip install "garminconnect>=0.3.11"` command. 3. Package resolution selects the affected release because it satisfies the open-ended version constraint. 4. Installation or import executes package-controlled code under the user's account. 5. The package can access credentials passed to `Garmin`, locally cached tokens, fetched health records, and files available to the user. This path requires a compromised dependency release, index, or dependency-resolution environmen ...[truncated 648 chars]
Remediation
View remediation
``` 3. Pin and hash transitive dependencies where the deployment system supports it. 4. Explicitly use a trusted package index and avoid dependency installation from arbitrary mirrors or extra indexes. 5. Establish a controlled update process: review release changes, run authentication and storage tests, verify package provenance, update hashes, and only then change the pinned version. 6. Consider isolating the Skill in a dedicated virtual environment with only the minimum dependencies required. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (24)

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

The documented authentication flow includes reading credentials from multiple local sources, including Keychain and a plaintext file. Credential access is expected for this skill’s purpose, but handling account secrets across several storage channels materially increases the chance of exposure if the host is misconfigured or the skill is run in an untrusted environment.

Content

Scanner excerpt · garmin_health.py (reported line 14)May include surrounding context.

python
Auth priority:
  1. --email / --password CLI args
  2. GARMIN_EMAIL / GARMIN_PASSWORD environment variables
  3. macOS Keychain: security find-generic-password -s 'garmin_connect'
  4. ~/.garmin_credentials (format: email=..., password=...)

Default data location:

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

This function actively loads Garmin credentials from environment variables, Keychain, and a local file. In a health-data skill, account compromise can expose not just login access but a broad history of sensitive wellness and activity information, so the credential-access surface is security-relevant even if functionally necessary.

Content

Scanner excerpt · garmin_health.py (reported line 246)May include surrounding context.

python
# ── Garmin Client ──────────────────────────────────────────────────────────

def _load_credentials(email_arg=None, password_arg=None):
    """Load credentials from args -> env -> macOS keychain -> file."""
    email = email_arg or os.environ.get("GARMIN_EMAIL")
    password = password_arg or os.environ.get("GARMIN_PASSWORD")

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · garmin_health.py (reported line 280)May include surrounding context.

python
"env": [
    {
      "name": "GARMIN_EMAIL",
      "description": "Your Garmin Connect account email address. Can also be set via macOS Keychain or ~/.garmin_credentials file.",
      "required": false
    },
    {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · garmin_health.py (reported line 797)May include surrounding context.

python
"env": [
    {
      "name": "GARMIN_EMAIL",
      "description": "Your Garmin Connect account email address. Can also be set via macOS Keychain or ~/.garmin_credentials file.",
      "required": false
    },
    {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · skill.json (reported line 27)May include surrounding context.

json
"env": [
    {
      "name": "GARMIN_EMAIL",
      "description": "Your Garmin Connect account email address. Can also be set via macOS Keychain or ~/.garmin_credentials file.",
      "required": false
    },
    {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · skill.json (reported line 32)May include surrounding context.

json
"env": [
    {
      "name": "GARMIN_EMAIL",
      "description": "Your Garmin Connect account email address. Can also be set via macOS Keychain or ~/.garmin_credentials file.",
      "required": false
    },
    {

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
- ✅ **仅与 Garmin 官方 API 通信** -- 无额外网络请求
- ✅ **支持 macOS 钥匙串** -- 密码可加密存储,无需明文
- ⚠️ **避免 --password 参数** -- 命令行密码会留在 shell 历史和进程列表中,建议用环境变量或钥匙串
- ⚠️ **保护配置文件** -- 使用 `~/.garmin_credentials` 时务必 `chmod 600`

## 常见问题

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · garmin_health.py (reported line 269)May include surrounding context.

python
- ✅ **仅与 Garmin 官方 API 通信** -- 无额外网络请求
- ✅ **支持 macOS 钥匙串** -- 密码可加密存储,无需明文
- ⚠️ **避免 --password 参数** -- 命令行密码会留在 shell 历史和进程列表中,建议用环境变量或钥匙串
- ⚠️ **保护配置文件** -- 使用 `~/.garmin_credentials` 时务必 `chmod 600`

## 常见问题

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documentation describes capabilities involving environment variables, shell commands, and local file writes, but it does not declare any explicit tool scope or permission boundaries. This can cause an agent platform to invoke the skill with broader-than-expected access, increasing the risk of credential exposure or unauthorized filesystem changes if the skill is auto-enabled.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

Option D -- Credentials file:

bash
echo -e "email=you@example.com\npassword=yourpassword" > ~/.garmin_credentials
chmod 600 ~/.garmin_credentials

3. Set region (China accounts only)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

Option D -- Credentials file:

bash
echo -e "email=you@example.com\npassword=yourpassword" > ~/.garmin_credentials
chmod 600 ~/.garmin_credentials

3. Set region (China accounts only)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are broad, natural-language health questions that could easily match ordinary user conversation and cause the skill to run unexpectedly. In this context, unintended activation is more sensitive because the skill accesses private health data and may prompt for or use stored credentials.

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 · garmin_health.py (reported line 62)May include surrounding context.

python
conn = sqlite3.connect(DB_PATH)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.executescript("""
    CREATE TABLE IF NOT EXISTS daily_summary (
        date TEXT PRIMARY KEY,
        fetched_at TEXT,
        steps INTEGER,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill reads Garmin credentials from CLI args, environment variables, Keychain, and a plaintext dotfile, but the handling and risks are only partially disclosed. Especially problematic is support for ~/.garmin_credentials, which encourages plaintext secret storage and increases the chance of credential leakage through filesystem exposure, backups, or misconfigured permissions.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · garmin_health.py (reported line 252)May include surrounding context.

python
if not password:
        try:
            kc = subprocess.run(
                ["security", "find-generic-password", "-s", "garmin_connect", "-w"],
                capture_output=True,
                text=True,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Beyond high-level health metrics, the skill fetches and stores detailed workout structure such as exercise sets, laps, and performance summaries. That expands collection of sensitive personal data beyond the plainly stated purpose, increasing privacy risk and the blast radius if local storage is accessed by others.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill persistently stores highly sensitive health and fitness data to JSON and SQLite on disk, but it provides no clear runtime consent, retention policy, or file-permission hardening. On multi-user systems, shared backups, synced home directories, or compromised endpoints, this can expose intimate medical and behavioral data well beyond the immediate execution of the skill.

Content

No source excerpt is available for this finding.

Tainted flow: 'cache_file' from os.environ.get (line 416, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
87% confidence
Finding

The JSON output path is derived from DATA_DIR, which can be controlled through the GARMIN_DATA_DIR environment variable, and the code writes to that path without validation. In contexts where untrusted environment variables can be injected or the skill runs with elevated privileges, this can redirect sensitive health data writes to unintended files or locations, causing data exposure or file clobbering.

Content

Scanner excerpt · garmin_health.py (reported line 756)May include surrounding context.

python
p(f"  本周运动: 中强度 {result.get('weekly_moderate_minutes')}min  高强度 {result.get('weekly_vigorous_minutes')}min / 目标 {result.get('weekly_intensity_goal')}min")

    # ── 保存 JSON ──────────────────────────────
    with open(cache_file, "w", encoding="utf-8") as f:
        json.dump(result, f, ensure_ascii=False, indent=2)

    if target_date == str(date.today()):

Tainted flow: 'latest_file' from os.environ.get (line 760, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
87% confidence
Finding

latest_file is built from DATA_DIR, which comes from an environment variable, and is written without any path safety checks. If an attacker can influence the process environment or filesystem layout, they may cause overwriting of arbitrary writable files or redirect sensitive health data into an attacker-observable location.

Content

Scanner excerpt · garmin_health.py (reported line 761)May include surrounding context.

python
if target_date == str(date.today()):
        latest_file = os.path.join(DATA_DIR, "latest.json")
        with open(latest_file, "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=2)

    # ── 写入 SQLite daily_summary ──────────────

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest explicitly states that sensitive health snapshots and OAuth tokens are stored locally, but it does not clearly warn users about the privacy and security implications of that persistence. Health telemetry and authentication tokens are both sensitive; if written to default home-directory locations without strong permissions, encryption, or disclosure, they may be exposed to other local users, backups, or malware.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The line explicitly describes the skill as having an "English UI," which can be a language/locale policy concern when no user choice, opt-in, or justification is provided. Under the policy rules, forcing a specific language without opt-in is reportable across all file types, including changelog text.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation states that all labels and output are in English, but does not indicate that this is optional or user-configurable. This is a natural-language policy concern because it imposes a specific language without explicit user opt-in or a documented necessity.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

Many user-facing strings, status messages, and warnings are hardcoded in Chinese, while the script description and argument help are partly in English. This imposes a locale/language choice on users without an explicit opt-in or documented region-specific justification.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The skill reaches outside normal API use and invokes the macOS Keychain through a system utility, but this capability is not disclosed in the stated purpose. While not inherently malicious, undisclosed credential-store access weakens informed consent and may surprise users reviewing the skill’s behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.