Back to skill

Security audit

Garmin Connect Pro

Security checks for vulnerabilities and agentic risk

Overview

This Garmin integration is mostly transparent about its purpose, but it handles sensitive health-account access and supports storing Garmin passwords and tokens locally in risky ways.

Review before installing. Prefer a dedicated Garmin account if possible, avoid the credentials.json option, do not put GARMIN_PASSWORD directly in cron/OpenClaw cron commands, protect or remove cached tokens when no longer needed, and consider pinning or auditing 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 (3)

T08 · Insecure Dependencies

Warning
Location
package.json:25
Finding
Unbounded Third-Party Dependency Permits Unaudited Future Releases<![CDATA[ ## Vulnerability Details **File Location**: `package.json:25-28` **Vulnerability Type**: Unbounded third-party dependency / supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```json "requires": { "bins": ["python3"], "pips": ["garminconnect>=0.2.38"] }, ``` The same unsafe installation pattern is documented in `README.md:69`, `SKILL.md:11`, and `SKILL.md:94`: ```bash pip3 install garminconnect ``` ### Technical Analysis The Skill permits any current or future `garminconnect` release greater than or equal to version `0.2.38`. No exact version, lock file, package hash, or trusted artifact digest constrains installation. This dependency occupies a particularly sensitive trust boundary: it receives the Garmin email, password, and cached OAuth tokens and performs all remote API communication. Although the audited project contains no evidence that the current dependency is malicious, the open-ended constraint means a future compromised, malicious, or behaviorally incompatible release could be installed without a corresponding review of this Skill. Because Python executes package initialization code during import, a compromised dependency would not need to wait for an explicit API request to run code under the invoking user's account. ### Attack Path 1. An attacker compromises the upstream package publisher, release process, distribution account, or another relevant package-distribution component. 2. The attacker publishes a release satisfying `garminconnect>=0.2.38`. 3. A user or automated Skill installer resolves and installs that release because no upper bound, exact pin, or hash is enforced. 4. `scripts/garmin.py` or `scripts/garmin-pro.py` imports the installed package. 5. Malicious package code executes with the operating-system privileges of the user running the Skill. 6. The package can access credentials supplied to it, cached Garmin tokens, returned health information, and other files available to that user. ### Impact ...[truncated 675 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `garminconnect` to a specifically reviewed version, for example: ```json "pips": ["garminconnect==<reviewed-version>"] ``` 2. Use a lock file containing hashes and install with hash verification, such as: ```bash pip install --require-hashes -r requirements.txt ``` 3. Install dependencies in a dedicated virtual environment under a low-privilege service account. 4. Review the dependency and its transitive dependencies before updating the version pin. 5. Update `README.md` and `SKILL.md` so installation examples use the same pinned, hash-verified dependency policy. 6. Consider generating and reviewing a software bill of materials for release artifacts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/garmin-pro.py:746
Finding
Interactive Login Stores the Garmin Password Before Applying Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/garmin-pro.py:746-749` **Vulnerability Type**: Plaintext credential storage with non-atomic permission hardening **Risk Level**: Medium ### Vulnerable Code ```python TOKEN_DIR.mkdir(parents=True, exist_ok=True) with open(CREDENTIALS_FILE, "w") as f: json.dump({"email": email, "password": password}, f) os.chmod(CREDENTIALS_FILE, 0o600) ``` ### Technical Analysis The interactive login flow permanently writes the user's Garmin email and password to `~/.config/garmin-connect/credentials.json` in plaintext. The file is opened and populated before `os.chmod(..., 0o600)` applies restrictive permissions. The initial file mode is therefore determined by the process umask. Under an unexpectedly permissive umask, the file may initially be readable by group members or other local users. Even when the eventual mode is `0600`, a local process monitoring the directory may open the file during the interval between its creation and the subsequent `chmod`. The design also retains the account password after OAuth tokens have been generated. That retention exceeds the minimum secret persistence necessary if valid refreshable tokens can support later sessions. ### Attack Path 1. The victim invokes the interactive `login` command in `scripts/garmin-pro.py`. 2. The script collects the Garmin email and password. 3. `open(CREDENTIALS_FILE, "w")` creates the file using permissions derived from the current umask. 4. The script writes the plaintext credentials and closes the file. 5. Before or during the later `chmod`, a local attacker or compromised process monitoring the directory opens and reads the file. 6. The attacker uses the captured password to authenticate to the Garmin account or access associated private data. A longer-term disclosure can also occur through backups, endpoint collection tools, or another process running under the same user, because the password remains on disk after login. ### Impact As ...[truncated 598 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not retain the account password after OAuth tokens have been acquired successfully. 2. Prefer an operating-system secret store such as macOS Keychain, Secret Service, or another approved credential manager. 3. If a credential file remains supported, create it atomically with mode `0600`, rather than applying permissions afterward: ```python fd = os.open( CREDENTIALS_FILE, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600, ) with os.fdopen(fd, "w") as f: json.dump({"email": email, "password": password}, f) ``` 4. Create and verify the parent directory with mode `0700`. 5. Use atomic replacement with a securely created temporary file when updating an existing credential file. 6. Reject symlinks and verify that the destination is a regular file owned by the current user. 7. Provide a migration command that removes stored passwords after confirming that cached tokens work. 8. Ensure logout and authentication-failure paths securely remove obsolete secrets. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
README.md:169
Finding
Recommended Cron Command Persists the Garmin Password in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `README.md:169-174` **Vulnerability Type**: Secret exposure through scheduled-task configuration and command environment **Risk Level**: Medium ### Vulnerable Code ```markdown ### Option A: Environment Variables (Recommended) ```bash # In your crontab or OpenClaw cron config: GARMIN_EMAIL="you@example.com" GARMIN_PASSWORD="your-password" python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py summary ``` ``` Equivalent guidance appears in `SKILL.md:227-231`: ```bash # In your crontab or OpenClaw cron config: GARMIN_EMAIL="your-email@example.com" GARMIN_PASSWORD="your-password" python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py summary ``` ### Technical Analysis The documentation describes this approach as using environment variables, but it embeds the password directly into the persistent scheduled-task command. A crontab or OpenClaw cron configuration is itself disk-backed configuration, so the claim that this approach avoids storing the password on disk is inaccurate. The secret may consequently appear in: - Crontab or scheduler configuration files. - Scheduler exports, administrative interfaces, and diagnostic output. - Configuration backups. - Process metadata or process-inspection tools, depending on how the scheduler invokes the command. - Shell history if the user enters the command interactively while creating the task. The scheduled functionality is legitimate and user-initiated; the Skill does not silently register persistence. The vulnerability is the recommended secret-delivery method, not the existence of optional fitness-summary scheduling. ### Attack Path 1. A user follows the recommended setup and substitutes their real Garmin password into the cron command. 2. The scheduler stores the complete command in persistent configuration. 3. A local user, administrator, backup reader, compromised scheduler component, or diagnostic collector accesses that configuration or r ...[truncated 654 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all examples that place `GARMIN_PASSWORD` directly in a crontab command. 2. Use cached OAuth tokens after an interactive login, without retaining the account password where supported. 3. Retrieve secrets at runtime from an operating-system keychain or approved secret manager. 4. If an environment file is unavoidable: - Store it outside the project. - Restrict the file and its parent directory to the service account. - Use mode `0600` for the file and `0700` for the directory. - Ensure it is excluded from backups and diagnostic bundles where appropriate. 5. Run scheduled jobs through a small wrapper that retrieves the secret securely rather than embedding it in scheduler configuration. 6. Use a dedicated, low-privilege automation account and protect scheduler configuration from unrelated users. 7. Correct the documentation to state that environment variables embedded in crontab configuration are stored persistently and are not inherently secret. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (41)

Credential Access

High
Category
Privilege Escalation
Content
**Option 2: Credentials File**
```bash
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json
```
- Stored in plaintext (any process with file access can read it)
Confidence
95% confidence
Finding
The README explicitly instructs users to store Garmin account credentials in a plaintext JSON file under the home directory. Even with mode 600, any process running as that user, malware in the user session, backups, shell history mistakes, or accidental file disclosure can expose the password and lead to account compromise.

Credential Access

High
Category
Privilege Escalation
Content
```bash
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json
```
- Stored in plaintext (any process with file access can read it)
- File permissions set to 600 (owner read/write only)
Confidence
95% confidence
Finding
This line continues the insecure credential-file pattern by combining a plaintext password file with a permission-setting command, which may give users a false sense of safety. The core risk remains theft of Garmin credentials from local disk by any code with access to the user account or copied backups.

Credential Access

High
Category
Privilege Escalation
Content
# Option B: Credentials file
mkdir -p ~/.config/garmin-connect
echo '{"email": "you@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json

# Login (generates OAuth tokens)
Confidence
95% confidence
Finding
The quick-start section repeats instructions to persist email and password in plaintext on disk, normalizing an insecure setup path. Because this skill handles health and activity data tied to a real account, account takeover could expose sensitive personal information in addition to service access.

Credential Access

High
Category
Privilege Escalation
Content
# Option B: Credentials file
mkdir -p ~/.config/garmin-connect
echo '{"email": "you@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json

# Login (generates OAuth tokens)
python3 scripts/garmin.py login
Confidence
95% confidence
Finding
This finding is substantively the same insecure practice: the user is told to create and keep a plaintext password file for later script use. Long-lived secrets on disk materially increase the likelihood and blast radius of compromise, especially for scheduled automation.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
ities
```bash
python3 scripts/garmin.py download          # List recent activities
python3 scripts/garmin.py download --id 123456 --format fit
python3 scripts/garmin.py download --id 123456 --format gpx
```

## Cron Job Setup

⚠️ **Security Note:** Using environment variables is more secure than storing credentials on disk.

### Option A: Environment Variables (Recommended)

```bash
# In your crontab or OpenClaw cron config:
GARMIN_EMAIL="you@example.com" GARMIN_PASSWORD="your-password" python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py summary
```

### Option B: Credentials File

```bash
# Morning motivation at 6:30
openclaw cron add --name "Morning Fitness" --cron "30 6 * * *" \
  --message "python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py summary"

# Midday check at 12:00
openclaw cron add --name "Midday Check" --cron "0 12 * * *" \
  --message "python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py ask 'body battery'"
```

**Note:** Cron jobs that
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### "Authentication failed"
- Verify email and password in credentials file or environment variables
- Delete tokens: `rm -rf ~/.config/garmin-connect/tokens/`
- Re-run login

### "No data for date"
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This mismatch is more serious because the skill appears to expose capabilities beyond the advertised scope while omitting declared permissions. In a credentialed health-data integration, inaccurate descriptions reduce informed consent and can hide access to additional personal data categories, persistent auth tokens, and export functions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
This mismatch is more serious because the skill appears to expose capabilities beyond the advertised scope while omitting declared permissions. In a credentialed health-data integration, inaccurate descriptions reduce informed consent and can hide access to additional personal data categories, persistent auth tokens, and export functions.

Credential Access

High
Category
Privilege Escalation
Content
required: false
          secret: true
      files:
        - path: ~/.config/garmin-connect/credentials.json
          description: "Fallback: Credentials file if env vars not set. WARNING: Plaintext - use env vars instead."
          required: false
          permissions: "600"
Confidence
96% confidence
Finding
The skill explicitly supports reading credentials from a plaintext JSON file in the user's home directory. Even with 600 permissions, any compromise of the user account, backup leakage, or accidental inclusion in sync/backup systems can expose Garmin credentials and associated health data.

Credential Access

High
Category
Privilege Escalation
Content
security:
      - "CREDENTIAL PRIORITY: Environment variables > Credentials file > macOS Keychain (if configured)"
      - "RECOMMENDED: Use GARMIN_EMAIL/GARMIN_PASSWORD env vars - not stored on disk, survives reboots with launchd/keychain"
      - "FALLBACK: Credentials file at ~/.config/garmin-connect/credentials.json (plaintext, 600 permissions)"
      - "OAuth tokens are cached locally after first login - subsequent logins use tokens, not password"
      - "Third-party library: garminconnect (https://github.com/cyberjunkie/garminconnect) - open source, auditable"
      - "NO data transmission except to Garmin API servers via official garminconnect library"
Confidence
95% confidence
Finding
The documented credential priority still normalizes a plaintext credentials file as a supported authentication source. That encourages insecure secret persistence for a skill handling personal health and activity data, increasing risk of account takeover and privacy loss if the local system is accessed.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
ntials.json (plaintext, 600 permissions)"
      - "OAuth tokens are cached locally after first login - subsequent logins use tokens, not password"
      - "Third-party library: garminconnect (https://github.com/cyberjunkie/garminconnect) - open source, auditable"
      - "NO data transmission except to Garmin API servers via official garminconnect library"
      - "For cron jobs: Pass env vars in crontab or use launchd with EnvironmentVariables key"
---

# Garmin Connect Pro

The most comprehensive Garmin Connect skill for OpenClaw. Retrieve activities, health data, sleep analysis, heart rate, stress, body battery, training readiness, VO2 max, and more from your Fenix, Forerunner, Index scales, or other Garmin devices.

## Security & Privacy

⚠️ **Important Security Information:**

### Credential Options

**Option 1: Environment Variables (Recommended for cron jobs)**
```bash
export GARMIN_EMAIL="your-email@example.com"
export GARMIN_PASSWORD="your-password"
```
- More secure for a
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
**Option 2: Credentials File**
```bash
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json
```
- Stored in plaintext (any process with file access can read it)
Confidence
97% confidence
Finding
The setup instructions directly tell users to create a plaintext file containing their email and password. This is dangerous because the skill context involves long-lived personal credentials and sensitive fitness/health data, making local secret theft especially impactful.

Credential Access

High
Category
Privilege Escalation
Content
```bash
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json
```
- Stored in plaintext (any process with file access can read it)
- File permissions set to 600 (owner read/write only)
Confidence
97% confidence
Finding
This line reinforces storing a password-bearing credentials file on disk, which remains sensitive even with restrictive file permissions. Local malware, shared-user environments, terminal history mistakes, or backup replication can still expose the secrets.

Credential Access

High
Category
Privilege Escalation
Content
# Option B: Use credentials file
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json

# Login (generates OAuth tokens)
Confidence
97% confidence
Finding
The first-time login workflow again encourages creating a plaintext credential file before performing authentication. Repetition of this pattern increases the chance users will adopt insecure storage for convenience and leave reusable credentials resident on disk.

Credential Access

High
Category
Privilege Escalation
Content
# Option B: Use credentials file
mkdir -p ~/.config/garmin-connect
echo '{"email": "your-email@example.com", "password": "your-password"}' > ~/.config/garmin-connect/credentials.json
chmod 600 ~/.config/garmin-connect/credentials.json

# Login (generates OAuth tokens)
python3 ~/.agents/skills/garmin-connect-pro/scripts/garmin.py login
Confidence
97% confidence
Finding
This instruction couples plaintext credential storage with token generation, creating two forms of credential material on disk: the password file and cached OAuth tokens. That expands the attack surface and can permit persistent unauthorized access even after the initial login.

Credential Access

High
Category
Privilege Escalation
Content
},
      "files": [
        {
          "path": "~/.config/garmin-connect/credentials.json",
          "description": "Garmin Connect login credentials (email + password). Used if env vars not set.",
          "required": false,
          "permissions": "600",
Confidence
88% confidence
Finding
The skill explicitly declares access to a plaintext credentials file containing Garmin email and password, which means the skill is designed to consume highly sensitive authentication material from disk. Even if this is for legitimate login purposes, storing and reading raw credentials increases the blast radius of any compromise, especially in an agent ecosystem where skills may be over-privileged or logs/errors may expose paths and contents.

Credential Access

High
Category
Privilege Escalation
Content
},
    "security": [
      "Credentials can be provided via GARMIN_EMAIL/GARMIN_PASSWORD environment variables OR credentials file",
      "If using credentials file: stored in plaintext at ~/.config/garmin-connect/credentials.json with 600 permissions",
      "OAuth tokens cached at ~/.config/garmin-connect/tokens/",
      "Third-party library 'garminconnect' handles all Garmin API communication - audit at https://github.com/cyberjunkie/garminconnect",
      "No direct external transmission except via garminconnect library to Garmin servers",
Confidence
80% confidence
Finding
The security metadata confirms that credentials may be stored in plaintext at a fixed filesystem location, documenting a sensitive secret-handling pattern rather than mitigating it. This makes the issue more credible because the skill context involves long-lived health-account access and cached OAuth tokens, so compromise could expose private fitness/health data and account access.

Credential Access

High
Category
Privilege Escalation
Content
sys.exit(1)

TOKEN_DIR = Path.home() / ".config" / "garmin-connect" / "tokens"
CREDENTIALS_FILE = Path.home() / ".config" / "garmin-connect" / "credentials.json"

# Emoji indicators
EMOJI = {
Confidence
95% confidence
Finding
The skill explicitly defines a plaintext credentials file path and elsewhere uses it to persist username/password material. In the context of a fitness-data integration, this is especially sensitive because the same account unlocks personal health and activity history, making credential theft materially harmful.

Missing User Warnings

High
Confidence
98% confidence
Finding
The login flow stores the user's Garmin email and password in plaintext JSON on disk at a predictable location. Even with chmod 0600, any local malware, backup leakage, multi-process compromise, or account-level intrusion can recover the credentials and reuse them against the Garmin account.

Credential Access

High
Category
Privilege Escalation
Content
sys.exit(1)

TOKEN_DIR = Path.home() / ".config" / "garmin-connect" / "tokens"
CREDENTIALS_FILE = Path.home() / ".config" / "garmin-connect" / "credentials.json"

# Emoji indicators
EMOJI = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

No suspicious patterns detected.