Back to skill

Security audit

Even-Effort Pace Chart

Security checks for vulnerabilities and agentic risk

Overview

The skill’s pacing tools and optional Garmin and weather integrations are disclosed, purpose-aligned, and appropriately scoped.

Use local files and manual weather for offline operation. Automatic forecasts share route midpoint locations with Open-Meteo before forecast confirmation. Supply a Garmin key only when requesting activity downloads, and protect downloaded activities and exported reports, which may contain personal activity or location information. Chart labels remain Chinese. Live integrations and optional dependency contents were not independently validated.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (35)

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Local FIT parsing, validation, and route preparation support the declared pacing and comparison workflows. However, authenticated access to a Garmin account—including credential use, activity discovery, and remote downloads—is a distinct capability not disclosed by the description or declared permissions. The client uses Garmin endpoints and restrictive output-file permissions; there is no evidence here of unrelated exfiltration. The mismatch is the undisclosed account/API integration, not the supporting FIT-processing details.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The inspect and generate commands align with the declared purpose. However, the list and fetch commands instantiate GarminClient with api_key(args.env_file) and invoke activities(...) or fetch(...). These expose remote account-data retrieval beyond the described conversion and comparison of supplied route/FIT files. The imported client's implementation is not shown, so its exact endpoints and credential handling cannot be verified. Local report writing, checkpoint interpolation, and configuration hashing are supporting implementation details, not mismatches.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The primary route-processing and chart-generation behavior matches the description. However, time-based water planning can automatically invoke fetch_water_forecast when weather information is missing or stale, making HTTPS requests containing segment midpoint coordinates. This third-party location disclosure occurs before the subsequent weather-confirmation step and is not disclosed in the declared description or permissions. Local exports and route-processing details are consistent supporting functionality. The supplied chunk does not establish the separate FIT actual-versus-plan comparison capability, but that absence alone is not treated as a mismatch.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Most tested functionality matches the description. However, the Garmin authentication, activity discovery, and download workflow introduces external-service and credential access not disclosed by the declared purpose. This is a distinct data-acquisition capability rather than merely a file-processing implementation detail. The tests demonstrate this capability through mocked requests; they do not themselves establish live network access or malicious credential handling.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Most chart generation and checkpoint calculations support the declared purpose. However, the tests demonstrate an additional weather-backed hydration workflow involving an external service and disclosure of route-derived coordinates. That is a material resource-access capability absent from the description and declared permissions. The tests establish that default operation remains offline and confirmed or manual weather data can avoid further requests. This chunk mocks networking rather than performing live requests, so it demonstrates the expected integration behavior, not an observed live transmission.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · references/comparison.en.md (reported line 100)May include surrounding context.

bash
python3 <skill-dir>/scripts/compare_activity.py list \
  --start-date 2026-10-01 --end-date 2026-11-02 --limit 20 --env-file .env.local

python3 <skill-dir>/scripts/compare_activity.py fetch <activity-id> --env-file .env.local

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · references/comparison.en.md (reported line 102)May include surrounding context.

python3 /scripts/compare_activity.py list
--start-date 2026-10-01 --end-date 2026-11-02 --limit 20 --env-file .env.local

python3 /scripts/compare_activity.py fetch --env-file .env.local

text

`list` supports pagination with `--start`/`--limit`. `fetch` saves `activity-details.json` and `activity-original.zip` under `output/activities/<activity-id>/` by default. Use `--output-dir` for another directory and `--overwrite` to replace a cache. Downloaded files have mode `600`; bearer tokens stay in memory.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · references/comparison.md (reported line 100)May include surrounding context.

bash
python3 <skill-dir>/scripts/compare_activity.py list \
  --start-date 2026-10-01 --end-date 2026-11-02 --limit 20 --env-file .env.local

python3 <skill-dir>/scripts/compare_activity.py fetch <activity-id> --env-file .env.local

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · references/comparison.md (reported line 102)May include surrounding context.

python3 /scripts/compare_activity.py list
--start-date 2026-10-01 --end-date 2026-11-02 --limit 20 --env-file .env.local

python3 /scripts/compare_activity.py fetch --env-file .env.local

text

`list` 可用 `--start`/`--limit` 分页。`fetch` 默认将 `activity-details.json`、`activity-original.zip` 保存到 `output/activities/<activity-id>/`;可用 `--output-dir` 改目录,替换缓存需 `--overwrite`。下载文件权限为 `600`,bearer token 仅留在进程内。

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · tests/test_activity_comparison.py (reported line 707)May include surrounding context.

python
class NetworkAndCliTests(unittest.TestCase):
    def test_env_precedence_and_no_shell_evaluation(self):
        with tempfile.TemporaryDirectory() as folder:
            path = Path(folder) / ".env"
            path.write_text("GARMIN_PERSONAL_API_KEY='file-key'\n")
            self.assertEqual(data.api_key(path, {"GARMIN_PERSONAL_API_KEY": "env-key"}), "env-key")
            self.assertEqual(data.api_key(path, {}), "file-key")

Unvalidated Output Injection

High
Category
Output Handling
Confidence
95% confidence
Finding

Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.

Content

Scanner excerpt · tests/test_pace_chart.py (reported line 757)May include surrounding context.

python
config = Path(self.temp.name) / "config.json"
        config.write_text('{"target":"05:00","start_time":"06:00"}')
        output = Path(self.temp.name) / "out"
        result = subprocess.run(
            [sys.executable, str(SCRIPT), "generate", str(self.path),
             "--config", str(config), "--output-dir", str(output)],
            capture_output=True, text=True,

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Chart labels are forced to Chinese without user opt-in.

Medium
Category
Output Handling
Confidence
98% confidence
Finding

L90 requires chart labels to remain Chinese even when delivery is in the user's language. It offers no label-language choice or justification for this restriction, imposing a specific output language without user opt-in.

Content

No source excerpt is available for this finding.

Chart output is restricted to Chinese labels without user language opt-in.

Medium
Category
Output Handling
Confidence
93% confidence
Finding

The reference explicitly states that chart labels remain Chinese and that bilingual documentation does not change output labels. This imposes a fixed output language without offering a chart-language choice or documenting a domain-specific justification.

Content

No source excerpt is available for this finding.

Chart labels are fixed to Chinese without user language opt-in.

Medium
Category
Output Handling
Confidence
97% confidence
Finding

L317 explicitly states that chart labels remain Chinese and that bilingual documentation does not change the output language. This imposes a specific output language without offering a user choice or explaining a domain-specific reason for the restriction.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · references/model.en.md (reported line 31)May include surrounding context.

md
rding intermediate elevation observations. Each observation is located by its spatial projection onto the retained chord. Backward or coincident projections introduce extra anchors, keeping distances strictly increasing and preventing elevation from being moved across a CP. Elevation order is retained, and CPs are projected onto the processed geometry. Explicit cumulative distances use this processed distance basis too; recheck CPs after enabling simplification instead of reusing old projections.

With simplification disabled, a 5 m tolerance trial still diagnoses sensitivity. A reduction exceeding `max(10 m, 1% of original distance)` produces a warning without changing distance. GPX `hdop > 10`, `vdop > 20`, `fix=none/unknown`, and invalid quality values are diagnostic only; they do not automatically delete points. Missing quality fields do not establish accuracy. Timestamps may come from route-planning software, so they do not trigger automatic speed filtering.

### Elevation spikes

Adds authenticated Garmin account access beyond the manifest's route conversion and FIT comparison scope.

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

In scripts/activity_data.py, L210–L226 read a Garmin API credential from the environment or an explicitly selected file, while L229–L300 authenticate remotely, search account activities, and download activity details and original files. The manifest describes processing routes and recorded FIT inputs, but does not disclose remote account browsing or authenticated activity acquisition; these are additional capabilities rather than necessary steps in parsing supplied tracks.

Content

No source excerpt is available for this finding.

Exposes remote Garmin activity listing and download commands not disclosed in the manifest.

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

In scripts/compare_activity.py, L220–L229 implement independently callable list and fetch workflows that load an API credential, query Garmin activities, and save downloaded activity files. These commands go beyond comparing a supplied FIT activity with a checkpoint plan, and remote account access is not disclosed by the skill's manifest.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · scripts/pace_chart.py (reported line 478)May include surrounding context.

python
for a, b in zip(samples, samples[1:])),
    }
    if diagnostics.get("quality_flagged_points") or diagnostics.get("invalid_quality_fields"):
        warnings.append("GPX contains low-quality or invalid positioning metadata; points were not automatically removed.")
    return Route(points, samples, route.candidates, warnings, diagnostics)

The skill transmits route-derived locations to an external weather service, beyond the manifest's pace-chart conversion and activity-comparison scope.

Medium
Category
Excessive Agency
Confidence
97% confidence
Finding

Time-based water planning can automatically send route-derived segment midpoint coordinates to Open-Meteo without first obtaining explicit permission for location disclosure. Coordinates are rounded to four decimal places, which still exposes relatively precise locations and potentially reveals private activity areas across multiple requests. Weather lookup is relevant to hydration planning, but the confirmation step approves the returned forecast only after transmission has occurred.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

The HTTPS request includes route-derived latitude and longitude in query parameters sent to an external provider, where they may be logged alongside request metadata. This is the transmission mechanism underlying the SDI-1 privacy finding, not a separate exfiltration channel; the fixed, legitimate weather endpoint and conditional invocation reduce suspicion of malicious intent but do not establish informed consent.

Content

Scanner excerpt · scripts/pace_chart.py (reported line 922)May include surrounding context.

python
"hourly": ",".join(WEATHER_FIELDS), "forecast_days": 16,
            "timezone": "GMT", "timeformat": "unixtime",
        })
        request = Request("https://api.open-meteo.com/v1/forecast" + query,
                          headers={"Accept": "application/json"})
        try:
            with urlopen(request, timeout=10) as response:

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 · tests/test_activity_comparison.py (reported line 256)May include surrounding context.

python
def test_standalone_fit_cli_reports_optional_dependency_as_json(self):
        script = Path(pace.__file__)
        result = subprocess.run([sys.executable, "-S", str(script), "inspect", str(self.path)],
                                capture_output=True, text=True)
        self.assertEqual(result.returncode, 2)
        self.assertIn("garmin-fit-sdk", json.loads(result.stderr)["error"])

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 · tests/test_behavior_evals.py (reported line 161)May include surrounding context.

python
path = Path(directory) / "candidate.json"
            for target, code in (("05:00", 0), ("06:00", 1)):
                path.write_text(json.dumps({"target": target}))
                result = subprocess.run([
                    sys.executable, str(EVALS / "evaluate.py"), "--case", "target_only",
                    "--config", str(path),
                ], capture_output=True, text=True)

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 · tests/test_behavior_evals.py (reported line 170)May include surrounding context.

python
def test_prompt_cli_outputs_a_variant_without_expected_results(self):
        case = next(case for case in evaluate.SUITE["cases"] if case["id"] == "replace_target_list")
        result = subprocess.run([
            sys.executable, str(EVALS / "evaluate.py"), "--case", case["id"],
            "--prompt", "--language", "en", "--variant", "2",
        ], capture_output=True, text=True)

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 · tests/test_behavior_evals.py (reported line 186)May include surrounding context.

python
command = [
            sys.executable, str(EVALS / "evaluate.py"), "--case", case["id"], "--prompt",
        ]
        result = subprocess.run(command, capture_output=True, text=True)
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertEqual(json.loads(result.stdout)["prompt"], case["prompt_zh"])
        for variant in ("0", "999"):