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## 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
## 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## 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
## 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## 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
## 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## 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
## 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## 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
## 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.

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.

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.

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.

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.

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.

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.

Lp3

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.

Natural-Language Policy Violations

Medium
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.

Missing User Warnings

Medium
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.

Natural-Language Policy Violations

Medium
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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Description-Behavior Mismatch

Medium
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.

Context-Inappropriate Capability

Medium
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.

Missing User Warnings

Medium
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.

Description-Behavior Mismatch

Medium
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.

Description-Behavior Mismatch

Medium
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.

Missing User Warnings

Medium
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.

Context-Inappropriate Capability

Low
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.

Natural-Language Policy Violations

Low
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.

Static analysis

No suspicious patterns detected.