Back to skill

Security audit

Fitness Coach Lite

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent workout-tracking skill, but it broadly auto-captures and persists sensitive health/body data without enough user control or privacy safeguards.

Install only if you are comfortable with this skill storing workout, body, sleep, menstrual, injury, and free-form note data locally from broad chat-style inputs. Use explicit commands, review the generated records, avoid importing plans from arbitrary paths, and protect or periodically delete the data directory if the machine or backups are shared.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/workout.py:61
Finding

Sensitive Health Data Stored Without Enforced Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/workout.py:61-63
Vulnerability Type: Plaintext sensitive-data storage with insufficient file-permission enforcement
Risk Level: Medium

Vulnerable Code

python
def save_json(path, data):
    with open(path, "w") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

Sensitive workout and health data is passed to this function at locations including:

python
save_json(HISTORY_FILE, hist_data)
python
save_json(BODY_FILE, data)

Technical Analysis

The application stores personal health information in data/history.json and data/body_status.json. This can include injuries, menstrual status, sleep condition, height, weight, body-fat percentage, physical limitations, goals, and free-form notes.

save_json() creates or overwrites these files using Python's default open(path, "w") behavior. It does not explicitly set a restrictive mode such as 0600, so newly created file permissions depend on the process umask. It also does not correct insecure permissions on existing files. On a shared host or under a permissive umask, another local account or process may be able to read this sensitive information.

The data is also stored as plaintext. The principal confirmed weakness is the lack of enforced filesystem access controls; encryption at rest may additionally be appropriate depending on the deployment threat model.

Attack Path

  1. A user records body measurements or a sensitive status through the skill.
  2. The relevant command passes the resulting object to save_json().
  3. save_json() creates or overwrites history.json or body_status.json without explicitly restricting its permissions.
  4. If the runtime umask or pre-existing permissions allow access, another local user or process reads the JSON file directly.
  5. The attacker obtains the stored health, fitness, and free-form note data.

Exploitation ...[truncated 629 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create the data/ directory with mode 0700 and verify its permissions during initialization.
  2. Create sensitive JSON files with mode 0600, using os.open() with explicit flags and permissions rather than relying on the ambient umask.
  3. Apply os.chmod(path, 0o600) to existing sensitive files before or after replacement so legacy permissions are corrected.
  4. Write updates atomically to a temporary file in the protected directory, set mode 0600, flush and synchronize the file, and then replace the destination with os.replace().
  5. Consider encryption at rest when local administrators, backups, or storage-layer actors are part of the threat model.
  6. Document what health data is retained and provide secure deletion and retention controls.

Example hardened approach:

python
import json
import os
import tempfile

def save_json(path, data):
    directory = os.path.dirname(path)
    os.makedirs(directory, mode=0o700, exist_ok=True)
    os.chmod(directory, 0o700)

    fd, temporary_path = tempfile.mkstemp(dir=directory)
    try:
        os.fchmod(fd, 0o600)
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
            f.flush()
            os.fsync(f.fileno())
        os.replace(temporary_path, path)
        os.chmod(path, 0o600)
    except Exception:
        try:
            os.unlink(temporary_path)
        except FileNotFoundError:
            pass
        raise
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (26)

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 · SKILL.md (reported line 334)May include surrounding context.

md
backend/.vendor/
backend/.venv/
.pytest_cache/
.env
.data/
*.log
*.lock

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill advertises tracking of sensitive health-related data, including body weight, body fat, menstrual status, injury indicators, sleep quality, and reports, but provides no user-facing notice about storage, retention, sharing, or deletion. In this context, silent accumulation of longitudinal health data materially raises privacy risk if the host environment, logs, backups, or exports are accessed by others.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The example trigger phrases are broad natural-language utterances like '今天很累', '走了8000步', and '给我周报', which overlap with ordinary chat and can cause the skill to activate when the user did not explicitly intend to invoke it. In a Telegram-native always-available context, unintended activation can capture health, workout, and body-status information and produce state changes or reports without sufficiently clear user intent.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill describes file-writing behavior and environment-variable access but does not declare any explicit tool/permission scope. That creates an over-privileged and non-transparent execution boundary where the agent may invoke sensitive capabilities without a clear contract or restriction, increasing the chance of unintended data modification.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill is configured to load whenever the user sends any exercise, status, or query-related message, which is an extremely broad trigger boundary. Over-broad activation can cause the agent to engage and perform writes on casual conversation that merely mentions fitness topics, leading to unwanted persistence and behavior surprises.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Instructing the agent to evaluate every user message for one of several operational intents creates an ambiguous boundary between normal conversation and state-changing actions. In a low-friction Telegram flow, this increases the likelihood of accidental logging, plan changes, or report generation from loosely related statements.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill explicitly states that each log call overwrites the day's workout record and instructs the agent to rewrite the complete exercise list after every set, yet provides no user-facing warning. Silent overwrite semantics create a material integrity risk: partial parsing, dropped exercises, or misclassification can erase or corrupt prior workout history without the user realizing it.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Several trigger phrases are generic everyday expressions, so benign statements like '累' or '状态' may be interpreted as commands to log health data or modify recommendations. This ambiguity is more dangerous in a skill that emphasizes immediate writes with no confirmation, because it can silently store sensitive personal information.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The documented reset commands can delete or reset current-day or arbitrary-date records without any warning, confirmation, or recovery guidance. In a conversational interface, destructive commands without safeguards can be triggered accidentally or through misinterpretation, causing irreversible data loss.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The document states that timestamps should be converted to UTC+8 as the user's timezone, which hard-codes a locale assumption. Because the skill does not provide user opt-in or indicate that it is intentionally limited to a UTC+8 region, this is a natural-language locale policy issue.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

User-facing strings, usage guidance, prompts, and command examples throughout the file are written exclusively in Chinese, including required reply phrases like “请回复:‘30分钟’或‘15分钟’”. This imposes a language policy constraint without opt-in or an alternative locale path.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

plan_import accepts an arbitrary local file path and reads JSON from it, which is broader than necessary for a Telegram fitness coach skill. In an agent context, this can become a local file read primitive against the host environment, enabling exposure of unintended local data if the agent is induced to call the import function on attacker-chosen paths.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill exposes manual history mutation commands such as reset_date and add_record that allow deletion or fabrication of workout history, going beyond the described coaching/tracking flow. In an agent-integrated environment, broad mutation primitives increase the risk of unintended data tampering, misleading reports, and abuse through prompt-induced tool use, even if they do not directly enable system compromise.

Content

No source excerpt is available for this finding.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 498)May include surrounding context.

python
records = hist_data.get("records", [])
    else:
        records = hist_data  # 旧格式是数组
    cutoff_date = (_get_session_dt_local() - __import__('datetime').timedelta(days=days-1)).strftime("%Y-%m-%d")
    # 过滤 workout 类型
    workouts = [r for r in records if r.get("date", "") >= cutoff_date and r.get("type") == "workout"]
    if not workouts:

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 528)May include surrounding context.

python
records = hist_data.get("records", [])
    else:
        records = hist_data  # 旧格式是数组
    cutoff_date = (_get_session_dt_local() - __import__('datetime').timedelta(days=days-1)).strftime("%Y-%m-%d")
    # 过滤 workout 类型
    workouts = [r for r in records if r.get("date", "") >= cutoff_date and r.get("type") == "workout"]
    if not workouts:

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 616)May include surrounding context.

python
def _get_week_range(ref_date_str: str):
    """返回给定日期所在周的起止日期(周一~周日)"""
    d = datetime.strptime(ref_date_str, "%Y-%m-%d")
    monday = d - __import__('datetime').timedelta(days=d.weekday())
    sunday = monday + __import__('datetime').timedelta(days=6)
    return monday.strftime("%Y-%m-%d"), sunday.strftime("%Y-%m-%d")

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 617)May include surrounding context.

python
"""返回给定日期所在周的起止日期(周一~周日)"""
    d = datetime.strptime(ref_date_str, "%Y-%m-%d")
    monday = d - __import__('datetime').timedelta(days=d.weekday())
    sunday = monday + __import__('datetime').timedelta(days=6)
    return monday.strftime("%Y-%m-%d"), sunday.strftime("%Y-%m-%d")

def _get_month_range(ref_date_str: str):

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 625)May include surrounding context.

python
d = datetime.strptime(ref_date_str, "%Y-%m-%d")
    first = d.replace(day=1)
    if d.month == 12:
        last = d.replace(year=d.year+1, month=1, day=1) - __import__('datetime').timedelta(days=1)
    else:
        last = d.replace(month=d.month+1, day=1) - __import__('datetime').timedelta(days=1)
    return first.strftime("%Y-%m-%d"), last.strftime("%Y-%m-%d")

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 627)May include surrounding context.

python
if d.month == 12:
        last = d.replace(year=d.year+1, month=1, day=1) - __import__('datetime').timedelta(days=1)
    else:
        last = d.replace(month=d.month+1, day=1) - __import__('datetime').timedelta(days=1)
    return first.strftime("%Y-%m-%d"), last.strftime("%Y-%m-%d")

def _filter_records(hist_data, start_date: str, end_date: str, rec_type=None):

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 738)May include surrounding context.

python
hist_data = load_json(HISTORY_FILE, {"records": []})
    today_dt = datetime.strptime(ref, "%Y-%m-%d")
    # 最近4周
    four_weeks_ago = (today_dt - __import__('datetime').timedelta(weeks=4)).strftime("%Y-%m-%d")
    workouts_4w = _filter_records(hist_data, four_weeks_ago, ref, "workout")
    pv = hist_data.get("plan_versions", []) if isinstance(hist_data, dict) else []
    lines = [f"📅 **阶段总结(近4周)**", "━━━━━━━━━━━━━━━"]

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This section stores detailed body metrics including height, weight, body-fat percentage, limitations, notes, and history, which exceeds the manifest's described low-friction workout tracking scope. The added collection and persistence of sensitive health data increases privacy risk and expands the consequences of unauthorized access or misuse by the surrounding agent ecosystem.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Sensitive body metrics are persisted to local storage without any visible disclosure, consent, or retention safeguards in this code. For a fitness skill, these health-related data points are privacy-sensitive, and silently storing them creates a meaningful confidentiality and compliance risk even if no direct exfiltration path is shown here.

Content

No source excerpt is available for this finding.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/workout.py (reported line 810)May include surrounding context.

python
"""显示身体状态历史"""
    data = load_json(BODY_FILE, {})
    history = data.get("history", [])
    cutoff = (_get_session_dt_local() - __import__('datetime').timedelta(days=days-1)).strftime("%Y-%m-%d")
    recent = [h for h in history if h.get("date", "") >= cutoff]
    if not recent:
        return None

Vague Triggers

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The README mixes quick actions, example prompts, and command descriptions without clearly identifying which strings are exact triggers versus informal examples. That ambiguity increases the chance of accidental invocation and unintended writes to workout/history data, especially for users interacting in free-form chat.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

Terms like “报告” and “练什么” are relatively broad and may appear in ordinary chat or in contexts outside this skill’s intended reporting flow. The file does not provide constraints or negative examples to reduce collisions.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.