Back to skill

Security audit

Garmin Trail Running Roadmap & Training Plan

Security checks for vulnerabilities and agentic risk

Overview

The skill has a real Garmin training purpose, but it needs review because it handles credentials and sensitive health/location data and generates unsafe calendar-writing code.

Install only if you are comfortable giving the skill access to Garmin credentials, Garmin health/activity/location history, local token storage, and macOS Calendar. Avoid putting Garmin passwords in config.json or command-line arguments, review generated calendar scripts before running them, do not use untrusted race names, and treat exported FIT/GPX/HTML files as sensitive personal data.

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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sync_gen.py:29
Finding

Generated Python Code Injection Through Unsanitized Race Name

Content
View full analysis

Vulnerability Details

File Location: scripts/sync_gen.py:29-36
Vulnerability Type: Generated-code injection
Risk Level: High

Vulnerable Code

python
for plan in TRAINING_PLAN:
    event_date = race_date - datetime.timedelta(days=plan["days_before"])
    date_str = event_date.strftime("%Y-%m-%d")
    summary = f"{plan['summary']} - {race_name}"
    desc = plan['description']
    script_content += f'\n    {{"date": "{date_str}", "summary": "{summary}", "description": "{desc}"}},'

script_content += """

Technical Analysis

The command-line race_name value is interpolated directly into generated Python source code. It is not escaped or serialized as a safe Python string literal. A race name containing quotation marks, backslashes, newlines, or Python syntax can terminate the intended string and inject additional statements into the generated program.

The generated output is designed to be saved and executed to synchronize Calendar events. Consequently, this is not merely malformed output: execution of the generated file can run injected Python with the current user's privileges.

Attack Path

  1. An attacker supplies or persuades the user to use a crafted race name containing Python syntax.
  2. The user invokes sync_gen.py with that value.
  3. The script embeds the value into the generated events Python list without escaping it.
  4. The generated output is saved as a Python file, as anticipated by the documented synchronization workflow.
  5. When that file is executed, the injected Python statements run under the user's account.

Impact Assessment

Successful exploitation permits arbitrary Python code execution with the privileges of the user running the generated script. This can expose Garmin tokens, health and location data, local files, Calendar contents, and other resources accessible to that account. It could also be used to execute subprocesses or modify user files.

Remediation
View remediation

Remediation Suggestions

  • Avoid generating executable Python. Synchronize Calendar events directly from the original process using structured event objects.
  • If code generation is unavoidable, serialize every inserted value with a safe serializer such as repr() or json.dumps() rather than constructing source literals manually.
  • Reject control characters and unexpected newline sequences in user-provided names.
  • Independently escape values before embedding them in AppleScript; Python escaping alone does not make a value safe for another language.
  • Add tests using quotes, backslashes, newlines, braces, and attempted Python or AppleScript payloads.
  • Require explicit user confirmation before modifying Calendar data.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/garmin_auth.py:140
Finding

Garmin Password Can Be Exposed Through Plaintext Configuration and Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/garmin_auth.py:25-30, 66-70, 140-168; SKILL.md:21-23
Vulnerability Type: Insecure credential handling
Risk Level: Medium

Vulnerable Code

python
CONFIG_FILE = Path(__file__).parent.parent / "config.json"


def load_config():
    """Load credentials from config file."""
    if not CONFIG_FILE.exists():
        return None
    try:
        with open(CONFIG_FILE) as f:
            return json.load(f)
python
# Save region info to config for future get_client calls
config = load_config() or {}
config["email"] = email
config["is_cn"] = is_cn
with open(CONFIG_FILE, 'w') as f:
    json.dump(config, f, indent=2)
python
login_parser.add_argument("--email", help="Garmin account email (or set via env/config)")
login_parser.add_argument("--password", help="Garmin account password (or set via env/config)")
login_parser.add_argument("--cn", action="store_true", help="Use Garmin China region (is_cn=True)")

# ...

email = args.email
password = args.password
is_cn = args.cn

# Priority: CLI args > config.json > environment variables
config = load_config()
if not email or not password:
    if config:
        email = email or config.get("email")
        password = password or config.get("password")

# ...

if not email or not password:
    email = email or os.getenv("GARMIN_EMAIL")
    password = password or os.getenv("GARMIN_PASSWORD")

The Skill documentation explicitly permits storing email and password in a project-local config.json or passing the password on the command line.

Technical Analysis

A password stored in config.json remains plaintext in the project directory. The login process reloads the entire configuration and later rewrites it without explicitly setting restrictive file permissions. If the existing configuration contains a password, that password is preserved in the rewr ...[truncated 1249 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove support for plaintext passwords in config.json.
  • Remove the --password argument and prompt interactively with getpass.getpass() when authentication is required.
  • Prefer the operating system keychain or a dedicated secret manager for persistent credentials.
  • Store only non-secret settings, such as the selected Garmin region, in config.json.
  • If any sensitive file must be created, use exclusive creation and mode 0600; verify ownership and refuse symbolic links.
  • Add config.json and token paths to version-control ignore rules and clearly warn users not to upload them.
  • Prefer short-lived tokens and provide a documented token-revocation procedure.

T08 · Insecure Dependencies

Warning
Location
scripts/garmin_auth.py:14
Finding

Security-Critical Third-Party Python Dependencies Are Unpinned

Content
View full analysis

Vulnerability Details

File Location: scripts/garmin_auth.py:14-18; scripts/garmin_data.py:17-20; scripts/garmin_activity_files.py:57,105
Vulnerability Type: Unpinned dependency installation
Risk Level: Medium

Vulnerable Code

python
try:
    from garminconnect import Garmin, GarminConnectAuthenticationError, GarminConnectConnectionError
except ImportError:
    print("❌ garminconnect library not installed", file=sys.stderr)
    print("Install with: pip3 install garminconnect", file=sys.stderr)
    sys.exit(1)
python
try:
    from garminconnect import Garmin
except ImportError:
    print('{"error": "garminconnect not installed. Run: pip3 install garminconnect"}', file=sys.stderr)
    sys.exit(1)
python
if not HAS_FITPARSE:
    return {"error": "fitparse library not installed. Run: pip install fitparse"}
python
if not HAS_GPXPY:
    return {"error": "gpxpy library not installed. Run: pip install gpxpy"}

Technical Analysis

The project instructs users to install garminconnect, fitparse, and gpxpy without exact versions, cryptographic hashes, a lockfile, or a declared trusted package source. The effective code installed can therefore change after this Skill has been reviewed.

This is especially sensitive for garminconnect: the imported package receives the user's Garmin email, password, session tokens, and responses containing private health and activity data. A compromised or unexpectedly changed package release would execute with direct access to those assets.

Attack Path

  1. A user encounters one of the installation messages and runs the suggested unpinned pip install command.
  2. The configured package index supplies the latest package version, a compromised release, or a package from an unsafe mirror.
  3. Package installation or import executes attacker-controlled code.
  4. The malicious dependency reads credentials, to ...[truncated 461 chars]
Remediation
View remediation

Remediation Suggestions

  • Provide a reviewed dependency manifest with exact versions.
  • Use a lockfile and require hashes, such as pip hash-checking mode with --require-hashes.
  • Document the expected package index and disallow untrusted or fallback indexes.
  • Install dependencies in an isolated virtual environment with only the required packages.
  • Review dependency release provenance and monitor security advisories.
  • Avoid presenting mutable one-line installation commands as the primary setup method.

T08 · Insecure Dependencies

Warning
Location
scripts/garmin_chart.py:20
Finding

Remote JavaScript Executes in Dashboards Containing Sensitive Health Data

Content
View full analysis

Vulnerability Details

File Location: scripts/garmin_chart.py:20-29, 124
Vulnerability Type: Runtime remote dependency without integrity protection
Risk Level: Medium

Vulnerable Code

python
def generate_html(charts_data, title="Garmin Health Dashboard"):
    """Generate HTML with Chart.js visualizations."""

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title}</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
javascript
const chartsData = {json.dumps(charts_data)};

Technical Analysis

Each generated dashboard loads executable JavaScript from jsDelivr when opened. Although the URL specifies a Chart.js version, the document does not use Subresource Integrity and does not apply a restrictive Content Security Policy. The health metrics are embedded directly into the same document as JavaScript data.

If the CDN, distribution path, network trust boundary, or served artifact is compromised, the remote script executes in the dashboard context and can read the embedded Garmin data. It can then initiate outbound requests from the browser.

This behavior is not required for the core analysis capability because the charting library can be bundled locally, allowing the dashboard to operate entirely offline.

Attack Path

  1. The user generates a dashboard containing Garmin sleep, HRV, heart-rate, Body Battery, or activity data.
  2. The user opens the local HTML document in a browser.
  3. The browser retrieves Chart.js from the remote CDN.
  4. A compromised response executes in the dashboard context.
  5. The script reads chartsData and sends sensitive values to an attacker-controlled endpoint.

Impact Assessment

Exploitation can disclose all he ...[truncated 299 chars]

Remediation
View remediation

Remediation Suggestions

  • Vendor a reviewed Chart.js build inside the Skill and reference it locally.
  • If remote loading is retained, add a verified Subresource Integrity hash and an appropriate crossorigin attribute.
  • Apply a restrictive Content Security Policy that limits scripts to the expected artifact and blocks arbitrary outbound connections.
  • Prefer completely offline rendering because the document contains health information.
  • Document that opening the current dashboard causes a network request to a third-party CDN.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/garmin_activity_files.py:29
Finding

Garmin Activity Files Are Written to Predictable Shared Temporary Paths

Content
View full analysis

Vulnerability Details

File Location: scripts/garmin_activity_files.py:29-46
Vulnerability Type: Unsafe temporary-file handling
Risk Level: Medium

Vulnerable Code

python
def download_activity_file(client, activity_id, file_format="fit", output_dir="/tmp"):
    """Download activity FIT or GPX file."""
    try:
        output_path = f"{output_dir}/activity_{activity_id}.{file_format.lower()}"

        if file_format.lower() == "fit":
            data = client.download_activity(activity_id, dl_fmt=client.ActivityDownloadFormat.ORIGINAL)
        elif file_format.lower() == "gpx":
            data = client.download_activity(activity_id, dl_fmt=client.ActivityDownloadFormat.GPX)
        elif file_format.lower() == "tcx":
            data = client.download_activity(activity_id, dl_fmt=client.ActivityDownloadFormat.TCX)
        else:
            return {"error": f"Unsupported format: {file_format}"}

        with open(output_path, 'wb') as f:
            f.write(data)

The command-line interface also uses /tmp as its default output directory.

Technical Analysis

The filename is deterministically constructed from an activity identifier and format in the shared /tmp directory. The file is opened using ordinary truncating write mode, without exclusive creation, symbolic-link checks, restrictive permissions, or cleanup.

Another local user or process able to predict the activity identifier can prepare a symbolic link at the expected path before the download. Opening that path can then overwrite the symlink target with activity data. Independently, downloaded files can remain in /tmp, where their contents may be exposed depending on platform permissions and local access.

FIT, GPX, and TCX files can contain precise coordinates, timestamps, heart rate, elevation, cadence, power, and route history.

Attack Path

  1. A local attacker predicts or learns the Garmin activity identifier ...[truncated 849 chars]
Remediation
View remediation

Remediation Suggestions

  • Create a private temporary directory with tempfile.TemporaryDirectory() or tempfile.mkdtemp().
  • Create files atomically and exclusively with permissions limited to the current user, such as mode 0600.
  • Refuse symbolic links and validate that a user-selected output directory is trusted.
  • Avoid predictable names, or combine exclusive creation with cryptographically random names.
  • Delete temporary activity files promptly after parsing unless the user explicitly requests retention.
  • Warn users when exporting raw activity files because they can contain precise location and health data.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The finding indicates the skill may perform system-calendar writes despite declaring no permissions, and that some planning output may be fixed-template rather than dynamic as advertised. Undeclared write access to a user's calendar is risky because it modifies system data and can create persistence, misleading reminders, or privacy leakage through calendar entries.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises and references capabilities that require environment access, file I/O, and shell execution, but it declares no explicit tool scope or permissions. This weakens least-privilege controls and can cause the agent to run with broader access than users expect, especially given credential handling and script execution described in the file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest description is entirely in Chinese while the skill title and surrounding structure are otherwise not explicitly scoped to Chinese-only usage. This can indicate a language/locale constraint without user opt-in or clear justification, which matches the policy category for forced language behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill states that it will generate calendar sync code and execute it, but it does not give a user-facing warning that this can modify the user's macOS/iOS calendar. Silent or poorly disclosed state-changing actions are dangerous because they can alter personal data stores and create confusion, privacy exposure, or unwanted automation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The entire skill reference is written as a Chinese-only guide and includes a prescribed Chinese output artifact ('小红书笔记') without any indication that language selection is optional. Under the stated policy, forcing a specific language or locale without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script downloads Garmin activity files containing precise GPS traces and health-related telemetry, then writes them to disk in a predictable location without any user-facing notice, consent prompt, retention control, or permission hardening. In this skill context, the data is especially sensitive because trail-running activities can reveal home/work locations, routines, and physiological metrics, increasing privacy and safety risk if the local system is shared or compromised.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The download action uses an authenticated Garmin client to retrieve remote account activity data without a clear user-facing disclosure that remote personal fitness data will be accessed and downloaded. Because this skill is specifically designed to process Garmin training history and route data, silent account access can expose highly sensitive location and health information beyond what a user may expect from a local command.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script writes user-related authentication metadata to config.json, which is a file write affecting local state. Although token saving is logged, there is no comparable disclosure that the script also updates config.json with account information and region settings.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code reads Garmin account credentials from config.json and environment variables, which is a sensitive operation under the warning criteria for code files. While the file docstring mentions authentication and token storage, there is no explicit user-facing disclosure here that credentials will be sourced from local config or environment variables.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes a skill for automatically producing professional trail race roadbooks, dynamic training plans, and syncing them to a system calendar based on Garmin data and GPX tracks. This file instead generates local interactive health dashboards from Garmin wellness/activity metrics and opens or saves an HTML report, with no GPX handling, roadbook generation, training-plan creation, or calendar synchronization.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script exposes a profile-fetching mode that returns PII including the user's full name, display name, and email, then prints it to stdout. That data is not clearly necessary for generating trail roadbooks or training plans, so it increases privacy exposure and creates an avoidable data-minimization failure if logs, agent transcripts, or downstream tools capture the output.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script collects sensitive health data and profile information and serializes it directly to stdout, which may be captured by calling agents, logs, terminal history, orchestration layers, or debugging systems. In this skill context, the data includes sleep, HRV, heart rate, stress, activities, and profile details, making accidental disclosure more dangerous because the information is highly personal and exceeds what some workflows may strictly require.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script collects a wide range of highly sensitive health data, including body composition, hydration, stress, SPO2, respiration, intraday heart rate, and weigh-ins, which exceeds what is reasonably necessary for generating trail race roadbooks and training plans. This expands the privacy attack surface and creates unnecessary exposure of medical-adjacent personal data if the skill stores, transmits, logs, or processes it elsewhere.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest says the skill automates professional trail race roadbooks, dynamic training plans, and syncs them to a system calendar. This file instead implements point-in-time queries for heart rate, stress, Body Battery, and steps, which is broader Garmin-data inspection functionality and does not perform the stated roadmap/training/calendar tasks.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code generator produces a script that will invoke osascript to create Calendar events automatically, with no confirmation, dry-run mode, or explicit consent step before modifying a user's personal calendar. In the context of an agent skill, this is a real security and safety issue because generated output may be piped into execution by users or downstream automation, causing unintended state changes on the host system.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The stated purpose centers on trail race roadbooks, training plans, and calendar sync, but this file is dedicated to sleep, HRV, body battery, stress-capable health-dashboard data retrieval and visualization. While some recovery metrics may inform training, a standalone dashboard generator for broad Garmin wellness data is not explicitly justified by the manifest's narrower planning/synchronization intent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The generated AppleScript explicitly looks for calendars named iCloud and then 日历, embedding a locale-specific assumption in the skill behavior. This constitutes a natural-language locale bias without any opt-in, configuration, or documentation explaining that the script is intended for Chinese-language calendar setups.

Content

No source excerpt is available for this finding.