Back to skill

Security audit

Garmer

Security checks for vulnerabilities and agentic risk

Overview

Garmer mostly matches its Garmin health-data purpose, but it stores reusable account tokens and includes a git self-update path that can change installed code, so it needs review before use.

Before installing, treat this as a sensitive health-data tool. Use interactive login rather than passing `--password`, protect or relocate `~/.garmer/garmin_tokens`, avoid committing or sharing exported JSON files, and be careful sending outputs to AI/chat systems. Do not run `garmer update` unless you have verified the git remote and trust the incoming code.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
src/garmer/cli.py:73
Finding

Garmin Password Can Be Exposed Through Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: src/garmer/cli.py:73-79, src/garmer/cli.py:905
Vulnerability Type: Sensitive credential exposure through process arguments
Risk Level: Medium

Vulnerable Code

python
def cmd_login(args: argparse.Namespace) -> int:
    """Handle login command."""
    email = args.email or input("Garmin Connect email: ")
    password = args.password or getpass.getpass("Garmin Connect password: ")

    try:
        client = GarminClient.from_credentials(
            email=email,
            password=password,
            save_tokens=True,
        )
python
login_parser.add_argument("-p", "--password", help="Garmin Connect password")

Technical Analysis

The interactive login path correctly uses getpass.getpass(), which avoids echoing the password. However, the CLI also permits the password to be supplied through -p or --password.

Command-line arguments are commonly exposed through:

  • Shell history files
  • Process inspection utilities such as ps
  • /proc/<pid>/cmdline on Linux
  • Terminal session recording
  • CI/CD logs and automation telemetry
  • Process monitoring and endpoint security products

The password is legitimately transmitted to Garmin through the garth authentication library, and no evidence indicates transmission to an unrelated destination. The vulnerability is the local exposure created before authentication occurs.

Attack Path

  1. A user runs a command such as garmer login --email user@example.com --password SECRET.
  2. The shell records the full command in its history, or the operating system exposes it in the process argument list.
  3. Another local account, monitoring process, or log collector reads the exposed argument.
  4. The attacker uses the recovered password to authenticate to the victim's Garmin account.
  5. The attacker may access sensitive profile, activity, location-related, sleep, heart ...[truncated 582 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the -p and --password command-line options.
  • Require hidden interactive entry through getpass.getpass() for normal CLI use.
  • If non-interactive authentication is essential, accept credentials through a protected credential provider or operating-system keyring rather than process arguments.
  • If stdin must be supported, require an explicit option such as --password-stdin, document its risks, and ensure the value is never logged.
  • Add automated tests confirming that credential values do not appear in command output, error messages, or logs.
  • Recommend password rotation to users who have previously supplied passwords through the command line.

T09 · Insecure Skill Coding Practices

Warning
Location
src/garmer/auth.py:100
Finding

Reusable Garmin Tokens Are Saved Without Explicit Permission Hardening

Content
View full analysis

Vulnerability Details

File Location: src/garmer/auth.py:100-111
Vulnerability Type: Insecure storage of reusable authentication tokens
Risk Level: Medium

Vulnerable Code

python
def save_tokens(self) -> None:
    """
    Save authentication tokens to disk.

    Creates the token directory if it doesn't exist.
    """
    try:
        self.token_dir.mkdir(parents=True, exist_ok=True)
        garth.save(self.token_path)
        logger.info(f"Saved authentication tokens to {self.token_path}")
    except Exception as e:
        logger.warning(f"Failed to save tokens: {e}")

Technical Analysis

The application persists reusable Garmin authentication material under ~/.garmer/garmin_tokens, but it does not explicitly enforce owner-only permissions on either the token directory or token file.

Actual permissions consequently depend on the process umask, pre-existing directory permissions, and the behavior of the garth.save() dependency. On a system with a permissive umask or an unsafe pre-existing token directory, the file may become readable by other local users or processes.

The method also does not verify that the target path is a regular file owned by the current user. A pre-created symlink or otherwise unsafe path may redirect the write when the token location is controllable or the parent directory is writable by an attacker.

Attack Path

  1. The user authenticates with garmer login.
  2. save_tokens() creates the token directory and invokes garth.save() without explicitly applying restrictive permissions.
  3. A permissive umask, unsafe custom GARMER_TOKEN_DIR, insecure pre-existing directory, or dependency behavior leaves the token material accessible.
  4. Another local user or compromised process reads and copies the saved tokens.
  5. The attacker resumes the authenticated Garmin session using the stolen tokens.
  6. The attacker retrieves sensitive Garmin profile ...[truncated 521 chars]
Remediation
View remediation

Remediation Suggestions

  • Create the token directory with owner-only permissions:
    python
    self.token_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
    self.token_dir.chmod(0o700)
    
  • After saving, explicitly enforce mode 0600 on the token file.
  • Write tokens atomically through an owner-only temporary file in the same directory, then replace the destination.
  • Reject token paths that resolve through symbolic links.
  • Verify that the directory and file are owned by the current user before loading or overwriting tokens.
  • Warn or fail securely when an existing token directory is group-writable, world-writable, or accessible by other users.
  • Prefer an operating-system credential store or keyring where practical.
  • Document that the token file grants access to sensitive Garmin data and must be protected like a password.

T03 · Remote Payload Retrieval and Execution

Warning
Location
src/garmer/cli.py:804
Finding

Self-Update Command Retrieves and Installs Unverified Code from a Configured Git Remote

Content
View full analysis

Vulnerability Details

File Location: src/garmer/cli.py:19-26, src/garmer/cli.py:804-851
Vulnerability Type: Unverified remote code retrieval and update
Risk Level: Medium

Vulnerable Code

python
def _get_package_root() -> Path | None:
    """Get the root directory of the garmer package (where .git lives)."""
    # First, try walking up from this file's location (works for editable installs)
    current = Path(__file__).resolve().parent
    for _ in range(5):  # Walk up at most 5 levels
        if (current / ".git").exists():
            return current
        current = current.parent
python
def cmd_update(args: argparse.Namespace) -> int:
    """Handle update command - pull latest changes from git."""
    package_root = _get_package_root()

    if not package_root:
        print("Could not find garmer package root directory.", file=sys.stderr)
        print("Make sure garmer is installed from a git repository.", file=sys.stderr)
        return 1

    print(f"Updating garmer from {package_root}...")

    try:
        # Fetch first to see what's available
        subprocess.run(
            ["git", "fetch"],
            cwd=package_root,
            check=True,
            capture_output=True,
        )

        # Check if there are updates
        result = subprocess.run(
            ["git", "status", "-uno"],
            cwd=package_root,
            check=True,
            capture_output=True,
            text=True,
        )

        if "Your branch is up to date" in result.stdout:
            print("Already up to date.")
            return 0

        # Show what will change
        log_result = subprocess.run(
            ["git", "log", "--oneline", "HEAD..@{u}"],
            cwd=package_root,
            capture_output=True,
            text=True,
        )
        if log_result.stdout.strip():
            print("\nIncoming
...[truncated 2770 chars]
Remediation
View remediation

Remediation Suggestions

  • Prefer removing the self-update command and using the package manager's normal signed or reviewed release workflow.
  • If self-update is retained, restrict updates to a hardcoded, canonical HTTPS or SSH remote and verify the resolved remote before fetching.
  • Require cryptographically signed release tags or commits and verify signatures against pinned maintainer keys.
  • Pin updates to explicit release versions rather than automatically pulling the current upstream branch.
  • Display the remote URL, target revision, and changed files, then require explicit user confirmation before modification.
  • Resolve the exact distribution source directory instead of walking into arbitrary ancestor repositories.
  • Refuse to update if the detected repository contains unrelated parent-project content or does not match the expected project identity.
  • Avoid stating that changes take effect immediately; install reviewed artifacts into a new environment and support rollback.
  • Add integrity verification and retain the previous known-good version for recovery.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (55)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

Access to user profile data, personal information, device inventory, settings, and goals expands collection beyond the health metrics emphasized in the skill description. In a health-data context, under-disclosing additional personal-data access undermines informed consent and increases privacy risk because users may authorize a broader data pull than expected.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill contains self-update logic that locates its source repository and pulls code changes into the local installation. This is outside the stated Garmin data extraction purpose and is dangerous because it permits runtime code modification through external repository state, increasing supply-chain exposure and reducing deployment integrity.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

Executing external git commands gives this skill capabilities unrelated to accessing Garmin user data. In context, that broadens the attack surface by letting a health-data tool inspect and mutate local code repositories, which is especially risky if the environment or upstream remote is compromised.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README encourages exporting and AI-assisted analysis of highly sensitive health data, but it does not warn users about privacy risks, secondary sharing, retention, or securing exported files and tokens. In a health-data extraction skill, this omission materially increases the chance that users will expose regulated or intimate personal data to local files, third-party assistants, or logs without informed consent.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares no explicit tool scope even though its documented behavior requires shell execution, network access, environment-variable use, and local file writes. In an agent environment, missing scope declarations can cause overbroad runtime permissions, making credential handling, token storage, export operations, and update commands riskier than necessary.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill instructs users to enter Garmin credentials and states that tokens are stored locally, but it provides no warning about secret handling, token sensitivity, file permissions, or shared-machine risk. Because this skill processes health and account data, stolen tokens or reused credentials could expose highly sensitive personal information and enable ongoing account access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The export workflow writes potentially comprehensive health data to local JSON files without warning about the sensitivity of those files or the risk of leaving them unencrypted on disk. Exported sleep, heart rate, stress, body metrics, and activity history can create a durable privacy exposure if copied, backed up, synced, or accessed by other local users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The Python example shows plaintext credentials embedded directly in source code without any warning against hardcoding secrets. This pattern commonly leads to credential leakage through shell history, source control, logs, notebooks, or shared snippets, and is especially dangerous here because it grants access to personal health and account data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script retrieves and prints a wide range of sensitive personal and health information, including sleep, heart rate, stress, hydration, weight, body composition, and profile data, without any privacy warning, redaction, or guidance about safe output handling. In practice, users may run example code in shared terminals, notebooks, CI logs, screen shares, or shell histories, causing unintentional exposure of regulated or highly sensitive health data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This integration is explicitly designed to collect highly sensitive health data and format it for AI/chat consumption, yet it provides no user-facing notice, consent checkpoint, or minimization control before that data is packaged for downstream processing. In the context of a health-data skill, that increases privacy risk because users may unknowingly expose sleep, heart-rate, stress, and other biometric information to broader AI or chat surfaces.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest says the skill extracts Garmin Connect health and fitness data and is for Garmin data, metrics, sleep analysis, or health insights. This file goes further by generating normative recommendations such as exercise, sleep, and bedtime advice, which is broader than straightforward data extraction and analysis.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documentation describes broad access to highly sensitive health and profile information, including sleep, heart rate, stress, body composition, birth date, email, and exported historical data, without any visible privacy notice, minimization guidance, or disclosure expectations. In a health assistant context, this increases the risk of collecting, exposing, or using regulated/sensitive personal data without informed user awareness or least-privilege constraints.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The reference expands the skill's effective capabilities beyond the stated metadata scope by documenting access to hydration, respiration, profile, device, export, and reporting features. In an agent setting, undocumented or under-disclosed capabilities can lead to over-collection of sensitive data and user consent mismatches, especially for health-related integrations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script retrieves and prints sensitive health and fitness data, including sleep, heart rate, stress, activities, and full snapshots, directly to stdout without any disclosure, confirmation, masking, or minimization. In an agent context, stdout may be surfaced to users, logs, transcripts, or other tooling, creating a real privacy exposure even if there is no obvious external exfiltration path in the code.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The integration collects sensitive health data and explicitly formats it for AI/chat consumption without any user-facing disclosure, consent check, or data-minimization guard. In a health-data context, this increases the risk of oversharing private biometric information to downstream systems or interfaces the user may not realize are receiving it.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The package-root discovery logic probes multiple local filesystem locations and package metadata to find a git repository, behavior that is unnecessary for the declared Garmin-data purpose. While not directly exploitable on its own, it expands local-environment awareness and supports the more dangerous self-update path.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The manifest emphasizes extracting and using Garmin data for metrics and insights, but this command persists bulk health/activity data to a user-specified local file. Writing sensitive health data to disk is a broader behavior than simply retrieving or presenting the data and is not mentioned in the manifest description.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The export command writes sensitive health and fitness data to disk without any warning, confirmation, or protective handling. In the context of a health-data skill, this increases the likelihood of accidental privacy exposure through insecure storage, shared machines, backups, or world-readable files.

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 · src/garmer/cli.py (reported line 813)May include surrounding context.

python
try:
        # Fetch first to see what's available
        subprocess.run(
            ["git", "fetch"],
            cwd=package_root,
            check=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 · src/garmer/cli.py (reported line 821)May include surrounding context.

python
)

        # Check if there are updates
        result = subprocess.run(
            ["git", "status", "-uno"],
            cwd=package_root,
            check=True,

Static analysis

No suspicious patterns detected.