Back to skill

Security audit

Strava Cycling Coach

Security checks for vulnerabilities and agentic risk

Overview

This Strava skill is mostly purpose-aligned, but it handles sensitive fitness data and long-lived account tokens while encouraging persistent background monitoring without enough containment or cleanup guidance.

Review before installing. Use it only if you are comfortable granting read access to private Strava activity/profile data and running a persistent cron job. Prefer tightening ~/.cache/strava to 0700 and its files to 0600, avoid logging full ride analyses, remove the cron entry when no longer needed, and treat the Strava refresh token and OAuth code as sensitive credentials.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cache_manager.py:17
Finding

Strava Activity and Health Data Cached Without Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/cache_manager.py:17-34
Vulnerability Type: Sensitive data stored with default filesystem permissions
Risk Level: Medium

python
def ensure_cache_dir():
    """Create cache directory if it doesn't exist."""
    CACHE_DIR.mkdir(parents=True, exist_ok=True)

def load_cached_activities():
    """Load activities from cache."""
    ensure_cache_dir()
    if not ACTIVITIES_CACHE.exists():
        return []

    with open(ACTIVITIES_CACHE) as f:
        return json.load(f)

def save_activities_to_cache(activities):
    """Save activities to cache."""
    ensure_cache_dir()
    with open(ACTIVITIES_CACHE, 'w') as f:
        json.dump(activities, f, indent=2)

    # Update last sync time
    with open(LAST_SYNC_FILE, 'w') as f:
        f.write(datetime.now().isoformat())

Technical Analysis

The cache directory and files are created without explicit permission modes. Their effective permissions therefore depend on the user's current umask. On a shared host or under a permissive configuration, other local users or processes may be able to read the files.

monitor_new_rides.py passes full activity objects returned by Strava to the cache manager. These objects can include activity names, timestamps, athlete-related fields, performance metrics, and potentially route or location summaries. The cache also has no retention limit and can grow indefinitely as new activities are merged.

Attack Path

  1. The user enables automatic monitoring or runs monitor_new_rides.py.
  2. The script retrieves recent activity objects from Strava.
  3. update_cache_with_new_activities() passes those objects to save_activities_to_cache().
  4. The objects are written to ~/.cache/strava/activities.json using permissions inherited from the environment.
  5. A local user or compromised process with read access to the file extracts fitness, timestamp, and pote ...[truncated 381 chars]
Remediation
View remediation

Remediation Suggestions

  • Create ~/.cache/strava with mode 0700.
  • Create activity and synchronization files with mode 0600, independent of the ambient umask.
  • Apply restrictive permissions to existing files before reading or updating them.
  • Use atomic writes through a securely created temporary file followed by os.replace().
  • Cache only the activity IDs and fields required to detect new rides rather than complete API responses.
  • Add a configurable retention limit and remove stale activity records.
  • Document that the cache contains sensitive activity information.

Example hardening:

python
import os
import tempfile

def ensure_cache_dir():
    CACHE_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
    CACHE_DIR.chmod(0o700)

def save_private_json(path, value):
    fd, temporary_path = tempfile.mkstemp(dir=CACHE_DIR, prefix=".tmp-")
    try:
        os.fchmod(fd, 0o600)
        with os.fdopen(fd, "w") as stream:
            json.dump(value, stream, indent=2)
        os.replace(temporary_path, path)
        path.chmod(0o600)
    except Exception:
        try:
            os.unlink(temporary_path)
        except FileNotFoundError:
            pass
        raise

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auto_analyze_new_rides.sh:5
Finding

Sensitive Ride Analyses Written to a Log Without Explicit Access Controls

Content
View full analysis

Vulnerability Details

File Location: scripts/auto_analyze_new_rides.sh:5-38
Vulnerability Type: Sensitive health and activity information written to an insufficiently protected log
Risk Level: Medium

bash
LOG_FILE="$HOME/.cache/strava/monitor.log"

mkdir -p "$(dirname "$LOG_FILE")"

echo "[$(date)] Checking for new rides..." >> "$LOG_FILE"

# Check for new rides
NEW_RIDES=$("$SCRIPT_DIR/monitor_new_rides.py" 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
    echo "[$(date)] Error checking for rides: $NEW_RIDES" >> "$LOG_FILE"
    exit 1
fi

echo "$NEW_RIDES" >> "$LOG_FILE"

# Extract ride IDs
RIDE_IDS=$(echo "$NEW_RIDES" | grep "^NEW_RIDE:" | cut -d: -f2)

if [ -z "$RIDE_IDS" ]; then
    echo "[$(date)] No new rides found" >> "$LOG_FILE"
    exit 0
fi

# Analyze each new ride
for RIDE_ID in $RIDE_IDS; do
    echo "[$(date)] Analyzing ride $RIDE_ID..." >> "$LOG_FILE"

    # Call analysis script
    # STRAVA_TELEGRAM_CHAT_ID should be set in environment or config
    "$SCRIPT_DIR/analyze_and_notify.py" "$RIDE_ID" >> "$LOG_FILE" 2>&1

Technical Analysis

The shell script creates the cache directory and monitor log without setting a restrictive umask or explicitly applying modes such as 0700 and 0600. The resulting permissions depend on the execution environment.

monitor_new_rides.py prints ride names and activity IDs. analyze_and_notify.py prints a generated report containing the ride name, date, duration, distance, power, heart rate, training load, cadence, elevation, and segment records. Because standard output and standard error are redirected to monitor.log, this sensitive information is retained in plaintext. No log rotation or retention policy is implemented.

Attack Path

  1. The user installs the documented cron entry.
  2. Cron executes auto_analyze_new_rides.sh every 30 minutes.
  3. A new virtual ride is detected and analyzed.
  4. Ride ide ...[truncated 642 chars]
Remediation
View remediation

Remediation Suggestions

  • Set umask 077 before creating the directory or writing any log content.
  • Create the cache directory with mode 0700 and the log with mode 0600.
  • Avoid logging complete generated analyses by default.
  • Log only operational events, such as timestamps, success status, and non-sensitive error codes.
  • Redact ride names, activity IDs, heart-rate values, and performance data.
  • Add log rotation, maximum size, and retention controls.
  • Avoid writing raw exception or HTTP response content where it might contain sensitive data.

Example hardening:

bash
umask 077
CACHE_DIR="$HOME/.cache/strava"
LOG_FILE="$CACHE_DIR/monitor.log"

mkdir -p -m 700 "$CACHE_DIR"
touch "$LOG_FILE"
chmod 600 "$LOG_FILE"

T09 · Insecure Skill Coding Practices

Note
Location
scripts/setup.sh:21
Finding

Strava Client Secret Entered Through Visible Terminal Input

Content
View full analysis

Vulnerability Details

File Location: scripts/setup.sh:21-22
Vulnerability Type: Sensitive credential exposed during interactive input
Risk Level: Low

bash
read -p "Enter your Client ID: " CLIENT_ID
read -p "Enter your Client Secret: " CLIENT_SECRET

Technical Analysis

The shell read command is used without the -s option when collecting the Strava client secret. Consequently, the secret is echoed visibly on the terminal while the user types it.

This is a local shoulder-surfing, terminal-recording, or session-capture weakness rather than a remote compromise. The configuration file is subsequently protected with mode 0600, but that does not prevent disclosure during initial input.

Attack Path

  1. The user executes scripts/setup.sh.
  2. The script prompts for the Strava client secret.
  3. The secret is displayed as plaintext while it is entered.
  4. A nearby observer, terminal recording utility, screen-sharing participant, or compromised session logger captures the value.
  5. The exposed application credential may then be reused in attempts against the Strava OAuth flow.

Impact Assessment

The exposed value is the Strava application's client secret. It does not directly provide access to the user's account without other OAuth material, but it weakens the application's authentication boundary and may assist impersonation or token-flow abuse. No system privileges are gained.

Remediation
View remediation

Remediation Suggestions

  • Read the client secret using silent terminal input.
  • Print a newline after the hidden prompt to preserve terminal formatting.
  • Avoid printing or logging the secret after collection.
  • Prefer an operating-system credential store when available.
  • Continue enforcing mode 0600 on the configuration file and mode 0700 on its parent directory.

Example:

bash
read -r -p "Enter your Client ID: " CLIENT_ID
read -r -s -p "Enter your Client Secret: " CLIENT_SECRET
printf '\n'

T09 · Insecure Skill Coding Practices

Note
Location
scripts/complete_auth.py:48
Finding

OAuth Authorization Code Passed Through Process Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/complete_auth.py:48-53
Vulnerability Type: Sensitive OAuth material exposed through command history and process metadata
Risk Level: Low

python
def main():
    if len(sys.argv) < 2:
        print("Usage: complete_auth.py <authorization_code>", file=sys.stderr)
        sys.exit(1)

    auth_code = sys.argv[1]
    config = load_config()

The documented invocation is:

bash
./scripts/complete_auth.py YOUR_CODE_HERE

Technical Analysis

The OAuth authorization code is accepted as a positional command-line argument. Command-line arguments may be recorded in shell history and can be visible in process listings or process metadata while the program is running.

Authorization codes are normally short-lived and single-use, which limits the exploitation window. Nevertheless, exposing them through argv is avoidable and can permit a local attacker to race the legitimate exchange if the code has not yet been redeemed.

Attack Path

  1. The user completes Strava authorization and receives an authorization code.
  2. The user invokes complete_auth.py with the code as a command-line argument.
  3. The command is retained in shell history or temporarily exposed through process inspection.
  4. A local attacker obtains the unredeemed code.
  5. If the attacker also has the application credentials and acts before the legitimate exchange completes, the attacker attempts to exchange the code for Strava access and refresh tokens.

Impact Assessment

Successful exploitation could grant the Strava API access authorized by the user, including access to private activity and profile data under the requested OAuth scopes. Exploitation requires local observation, access to the associated client credentials, and action within the short validity window. It does not grant operating-system privileges.

Remediation
View remediation

Remediation Suggestions

  • Do not accept the authorization code as a command-line argument.
  • Read it interactively with getpass.getpass() or from standard input.
  • Update SKILL.md and README.txt so they no longer instruct users to place the code on the command line.
  • Warn users to remove any previously entered authorization-code commands from shell history.
  • Preserve OAuth state and validate it during callback processing if a local callback handler is added.

Example:

python
from getpass import getpass

def main():
    auth_code = getpass("Enter the Strava authorization code: ").strip()
    if not auth_code:
        print("Error: authorization code is required.", file=sys.stderr)
        sys.exit(1)

    config = load_config()
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (35)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The file presents broad monitoring and analysis claims while the actual behavior reportedly includes credential/config usage and external API access without declared permissions. In this context, the dangerous part is not missing analytics features but the gap between user expectations and undisclosed access to account data and external services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The file presents broad monitoring and analysis claims while the actual behavior reportedly includes credential/config usage and external API access without declared permissions. In this context, the dangerous part is not missing analytics features but the gap between user expectations and undisclosed access to account data and external services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The file presents broad monitoring and analysis claims while the actual behavior reportedly includes credential/config usage and external API access without declared permissions. In this context, the dangerous part is not missing analytics features but the gap between user expectations and undisclosed access to account data and external services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The file presents broad monitoring and analysis claims while the actual behavior reportedly includes credential/config usage and external API access without declared permissions. In this context, the dangerous part is not missing analytics features but the gap between user expectations and undisclosed access to account data and external services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The file presents broad monitoring and analysis claims while the actual behavior reportedly includes credential/config usage and external API access without declared permissions. In this context, the dangerous part is not missing analytics features but the gap between user expectations and undisclosed access to account data and external services.

Content

No source excerpt is available for this finding.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

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

ent Secret 3. Visit an OAuth URL to authorize 4. Copy the authorization code and complete setup with:

bash
./scripts/complete_auth.py YOUR_CODE_HERE

3. Configure Automatic Monitoring (Optional)

To receive automatic ride analysis after each workout:

bash
# Set your Telegram chat ID
export STRAVA_TELEGRAM_CHAT_ID="your_telegram_chat_id"

# Add to your shell profile for persistence
echo 'export STRAVA_TELEGRAM_CHAT_ID="your_telegram_chat_id"' >> ~/.bashrc

# Set up cron job (checks every 30 minutes)
crontab -l > /tmp/cron_backup.txt
echo "*/30 * * * * $(pwd)/scripts/auto_analyze_new_rides.sh" >> /tmp/cron_backup.txt
crontab /tmp/cron_backup.txt

4. Test the Setup

Analyze your recent rides:

bash
./scripts/analyze_rides.py --days 90 --ftp YOUR_FTP

Usage

Get latest ride:

bash
scripts/get_latest_ride.py

Analyze specific ride:

bash
scripts/analyze_ride.py <activity-id>

Monitor for new rides (runs in background):

bash
scripts/monitor_rid

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/api.md (reported line 11)May include surrounding context.

md
1. **Authorize**: Direct user to authorization URL
2. **Exchange code**: Trade authorization code for access/refresh tokens
3. **Refresh**: Use refresh token to get new access token when expired

### Tokens

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/api.md (reported line 17)May include surrounding context.

md
1. **Authorize**: Direct user to authorization URL
2. **Exchange code**: Trade authorization code for access/refresh tokens
3. **Refresh**: Use refresh token to get new access token when expired

### Tokens

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/api.md (reported line 15)May include surrounding context.

md
### Tokens

- **Access Token**: Valid for 6 hours
- **Refresh Token**: Long-lived, use to get new access tokens
- **Expires At**: Unix timestamp when access token expires

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/api.md (reported line 16)May include surrounding context.

md
### Tokens

- **Access Token**: Valid for 6 hours
- **Refresh Token**: Long-lived, use to get new access tokens
- **Expires At**: Unix timestamp when access token expires

## Base URL

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/analyze_rides.py (reported line 23)May include surrounding context.

python
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/analyze_rides.py (reported line 25)May include surrounding context.

python
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/complete_auth.py (reported line 21)May include surrounding context.

python
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/get_latest_ride.py (reported line 22)May include surrounding context.

python
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/monitor_new_rides.py (reported line 23)May include surrounding context.

python
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.txt (reported line 34)May include surrounding context.

text
## Quick Start

1. Create Strava API app at https://www.strava.com/settings/api
2. Run `./scripts/setup.sh` and follow prompts
3. Complete OAuth with `./scripts/complete_auth.py CODE`
4. Test with `./scripts/analyze_rides.py --days 30`

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill documents capabilities that require shell execution, network access, environment variables, and file writes, but it does not declare any tool scope or permissions. This is dangerous because users and hosting platforms cannot accurately understand or constrain what the skill is allowed to do, increasing the chance of over-privileged execution and unintended access to local state or external services.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Overly broad activation phrases can cause the skill to trigger on generic exercise or performance questions outside the user's intent to access Strava data. In a skill with API access, local config, and optional background behavior, accidental invocation increases the risk of unnecessary data access or confusing consent boundaries.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

md
## Setup

### 1. Create Strava API Application

Visit https://www.strava.com/settings/api and create an application:
- Application Name: Clawdbot (or your preferred name)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill describes ongoing monitoring, periodic checks, and outbound Telegram notifications without a prominent warning that it will continue accessing account data and sending messages after setup. This is dangerous because users may enable persistent background processing and data egress without fully understanding the ongoing privacy and operational implications.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

The instructions add a cron job to persistently run a script every 30 minutes, establishing recurring execution beyond the current session. Persistent scheduled execution is security-sensitive because it expands the blast radius of any script behavior, bugs, token misuse, or later code changes, especially when paired with network access and notifications.

Content

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

echo 'export STRAVA_TELEGRAM_CHAT_ID="your_telegram_chat_id"' >> ~/.bashrc

Set up cron job (checks every 30 minutes)

crontab -l > /tmp/cron_backup.txt echo "*/30 * * * * $(pwd)/scripts/auto_analyze_new_rides.sh" >> /tmp/cron_backup.txt crontab /tmp/cron_backup.txt

text

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file describes access and refresh tokens, including that refresh tokens are long-lived, but it does not warn users that these credentials are sensitive and must be stored securely. For a markdown skill/reference file, omission of a warning about credential sensitivity is a user-disclosure gap with privacy and account-security implications.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documented stream keys include latlng and heartrate, which can expose precise location history and health-related data. The markdown does not disclose these privacy-sensitive behaviors or advise caution when accessing, storing, or sharing this data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script fetches detailed Strava activity data and heart-rate streams from the API, which involves transmitting sensitive personal fitness data using the user's access token. While there are technical docstrings, there is no meaningful user-facing warning in the script about accessing and transmitting this health-related data.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The manifest describes tracking and analyzing Strava rides and automatically providing performance analysis. Reading a Telegram destination from an environment variable introduces external messaging configuration and secret/context access that is not mentioned in the stated purpose or manifest description.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.