Back to skill

Security audit

Garmin

Security checks for vulnerabilities and agentic risk

Overview

This Garmin skill is mostly purpose-aligned, but it handles credentials and health data in ways that need careful review before installation.

Review this skill before installing. Use a dedicated low-privilege 1Password item or vault, avoid running the login script until the heredoc credential handling is fixed, replace /tmp/garmin-session with a private 0700 directory, set explicit cache permissions and retention, and install dependencies in a pinned virtual environment rather than system Python.

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

Error
Location
scripts/garmin-login.sh:18
Finding

Python Code Injection Through Credential Interpolation into a Here-Document

Content
View full analysis
/dev/null) PASSWORD=$(op item get "$GARMIN_1P_ITEM_NAME" --vault "$GARMIN_1P_VAULT" --fields password --reveal 2>/dev/null) if [ -z "$EMAIL" ] || [ -z "$PASSWORD" ]; then echo "❌ Credentials not found: '$GARMIN_1P_ITEM_NAME' in vault '$GARMIN_1P_VAULT'" echo " Set GARMIN_1P_ITEM_NAME / GARMIN_1P_VAULT if yours differ." exit 1 fi mkdir -p /tmp/garmin-session python - <
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/garmin-login.sh:27
Finding

Garmin Authentication State Stored in a Predictable Shared Temporary Directory

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cache-daily.sh:5
Finding

Sensitive Health Metrics Persisted Without Explicit Access or Retention Controls

Content
View full analysis
/dev/null) if [ -z "$STATS" ] || echo "$STATS" | grep -q '"error"'; then echo "⚠️ Could not fetch Garmin data" exit 1 fi CACHED=$(echo "$STATS" | jq ". + {cached_at: \"$(date -Iseconds)\", date: \"$TODAY\"}") echo "$CACHED" > "$CACHE_FILE" echo "✅ Cached Garmin data for $TODAY" echo "$CACHED" | jq . ``` ### Technical Analysis The script persistently stores Garmin-derived sleep, heart-rate, stress, training, and body-battery information in JSON files. It creates the cache directory and files without explicitly enforcing restrictive permissions. Consequently, confidentiality depends on the caller's umask and the permissions of all parent directories. The write is also non-atomic, allowing readers to observe partially written data and making replacement or redirection risks more significant if another principal can modify the cache directory. The implementation additionally conflicts with the privacy statement that data is queried on demand and not stored long-term by the Skill. The bundled `cache-daily.sh` script performs persistent storage and has no retention or deletion policy. ### Attack Path 1. The user runs `scripts/cache-daily.sh`, directly or through external automation. 2. The script retrieves detailed Garmin health metrics. 3. It creates a dated JSON file using the process's ambient umask and existing directory permissions. 4. Another local principal with read access to the directory or file reads the persisted health data. 5. Because no retention policy exists, historical records a ...[truncated 819 chars]
Remediation
View remediation
"$TMP_FILE" chmod 600 "$TMP_FILE" mv -f -- "$TMP_FILE" "$CACHE_FILE" ``` Further hardening should include: 1. Validate that `CACHE_DIR` is owned by the expected user and is not a symbolic link. 2. Enforce directory mode `0700` and file mode `0600`. 3. Implement configurable retention and secure deletion procedures. 4. Obtain clear user consent before enabling historical storage. 5. Update `SKILL.md` to state precisely what is stored, where it is stored, and for how long. 6. Avoid printing the complete cached health record to standard output unless explicitly requested. 7. Consider storing only the minimum metrics required for trend analysis rather than the complete response. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:25
Finding

Unpinned Third-Party Dependency Installed into System Python

Content
View full analysis
Remediation
View remediation
\ --hash=sha256: ``` Additional controls should include: 1. Remove `--break-system-packages` from the recommended installation path. 2. Commit a lock file or fully hashed requirements file. 3. Review dependency updates before adoption instead of automatically using the newest release. 4. Use trusted package indexes with TLS and, where feasible, an organization-controlled package mirror. 5. Run dependency vulnerability and provenance checks in continuous integration. 6. Execute the Skill under a dedicated, unprivileged account with access only to the required Garmin credential and data directories. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill claims to be a Garmin integration but also describes combined Garmin+Strava briefing behavior without declaring that external integration. This is a capability-boundary mismatch that can cause unintended cross-service data aggregation and surprise users about what accounts and data sources are involved.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The skill claims to be a Garmin integration but also describes combined Garmin+Strava briefing behavior without declaring that external integration. This is a capability-boundary mismatch that can cause unintended cross-service data aggregation and surprise users about what accounts and data sources are involved.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/get-stats.py (reported line 35)May include surrounding context.

python
result = subprocess.run(
        ['op', 'item', 'get', item_name, '--vault', vault, '--format', 'json'],
        capture_output=True, text=True,
        env={**os.environ, 'OP_SERVICE_ACCOUNT_TOKEN': op_token}
    )
    if result.returncode != 0:
        _die(f"1Password lookup failed for '{item_name}': {result.stderr.strip()}")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill describes shell commands, environment-variable use, and local file/token handling, but it declares no explicit tool scope or permissions. In an agent setting, undeclared shell/env capability increases the chance of over-privileged execution and makes review of what the skill can access much harder.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
72% confidence
Finding

The skill directs use of long-lived credentials in 1Password and mentions temporary session-token caching, which introduces persistence of authentication material beyond a single run. While this is common operational practice, it still increases exposure if local storage, cache directories, or the 1Password integration are misconfigured or accessed by other processes.

Content

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

md
### 2. Store Credentials in 1Password

Create a new "Login" item in your 1Password vault (e.g., "Personal") with the following details:

*   **Title:** `Garmin Connect` (or a custom name you prefer)
*   **Username:** Your Garmin Connect email address

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The skill processes sensitive health data including sleep, heart rate, stress, and recovery metrics, yet the privacy and caching implications are only mentioned later and weakly. Users may run the skill before understanding that health data and session artifacts may be cached locally, increasing confidentiality and compliance risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This shell script persists fetched Garmin metrics to disk using a dated JSON file, which is a safety-relevant data write involving personal fitness information. Although the script prints a success message after caching, it does not disclose beforehand that user data will be stored locally or describe the privacy impact in comments beyond a terse one-line summary.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This shell script reads a 1Password service account token and retrieves Garmin credentials, then uses them to perform a remote login. While failures are printed, there is no user-facing disclosure before the sensitive credential access and outbound authentication, and this file contains no comment or prompt warning that secrets will be read and used automatically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script writes authenticated session material to /tmp/garmin-session via client.garth.dump(), which creates local persistence of login state in a world-accessible temporary area. On multi-user systems or in environments with weak tmp hygiene, other local processes or users may read, copy, or reuse these session artifacts to access the Garmin account without the password.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill description says it fetches Garmin analytics, but the implementation also reaches into 1Password using a service-account token from the environment. This undisclosed secret-access capability is security-relevant because it broadens trust requirements and may surprise users or orchestrators that did not intend to grant password-manager access to a fitness stats skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script accesses OP_SERVICE_ACCOUNT_TOKEN and uses it to retrieve credentials from 1Password without any explicit disclosure in the skill context. In an agent ecosystem, undisclosed secret access is dangerous because users may authorize a benign-seeming Garmin skill without realizing it can consume password-manager-backed credentials.

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 · scripts/get-stats.py (reported line 32)May include surrounding context.

python
if not op_token:
        _die("OP_SERVICE_ACCOUNT_TOKEN not set")

    result = subprocess.run(
        ['op', 'item', 'get', item_name, '--vault', vault, '--format', 'json'],
        capture_output=True, text=True,
        env={**os.environ, 'OP_SERVICE_ACCOUNT_TOKEN': op_token}

Tainted flow: 'item_name' from os.getenv (line 25, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/get-stats.py (reported line 32)May include surrounding context.

python
if not op_token:
        _die("OP_SERVICE_ACCOUNT_TOKEN not set")

    result = subprocess.run(
        ['op', 'item', 'get', item_name, '--vault', vault, '--format', 'json'],
        capture_output=True, text=True,
        env={**os.environ, 'OP_SERVICE_ACCOUNT_TOKEN': op_token}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code persists Garmin session material to /tmp/garmin-session/, which may expose reusable authentication state to other local users/processes depending on file permissions and host configuration. Writing auth artifacts to a predictable shared temporary path without user disclosure increases the risk of session theft or unintended retention of sensitive health-account access.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes a Garmin-focused skill for fetching and analyzing Garmin Connect health and recovery metrics. This script also invokes a separate Strava skill to include external activity data, which broadens behavior beyond the stated Garmin integration scope.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

Multiple user-facing examples and descriptions refer specifically to 'Brian' (for example, asking whether Brian is recovered enough to train hard). This hard-coded personalization can violate language/locale-style organizational expectations for generic, reusable skills unless such targeting is explicitly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The usage description states that the command returns whether Brian is recovered enough for hard training. This is a natural-language policy concern because the skill presents individualized wording instead of neutral language suitable for broader use.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The top-level docstring presents the script as only fetching current Garmin Connect daily stats. In reality, the script also pulls credentials from 1Password, logs into Garmin Connect, and dumps session data to /tmp/garmin-session/, which materially broadens what the script does beyond the documented intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The script invokes other scripts to retrieve Garmin and Strava activity data, which are potentially privacy-sensitive data sources. Although the header comment mentions Garmin recovery and optional Strava activity, there is no user-facing prompt or runtime disclosure that personal fitness data is being accessed and displayed.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.