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

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.

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.

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.

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.

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

High
Category
YARA Match
Content
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
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
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
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
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
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### 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
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### 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
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return json.load(f)

def exchange_token(config, auth_code):
    """Exchange authorization code for access token."""
    url = "https://www.strava.com/oauth/token"
    
    data = {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
## 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`
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.

Lp3

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.

Vague Triggers

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

Session Persistence

Medium
Category
Rogue Agent
Content
## Setup

### 1. Create Strava API Application

Visit https://www.strava.com/settings/api and create an application:
- Application Name: Clawdbot (or your preferred name)
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.

Missing User Warnings

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

Session Persistence

Medium
Category
Rogue Agent
Content
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
```
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.

Missing User Warnings

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

Missing User Warnings

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

Missing User Warnings

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

Context-Inappropriate Capability

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

Static analysis

No suspicious patterns detected.