Back to skill

Security audit

Sleep Channel

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local sleep-tracking skill that stores and edits sleep log data as advertised, with some configuration and CSV-handling caveats.

Install only in a dedicated sleep-tracking channel and set WORKSPACE to a directory intended for this data. Treat the CSV as containing personal sleep history and Discord metadata; deleted entries may remain in the file with deleted status. If untrusted users can post to the channel, avoid opening the CSV directly in formula-capable spreadsheets unless cells are sanitized or formulas are disabled.

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/tracker.py:62
Finding

CSV Formula Injection Through Attacker-Controlled Sleep Notes

Content
View full analysis
None: with open(LOG_FILE, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=CSV_FIELDS) writer.writeheader() writer.writerows(rows) ``` ```python def parse_event(text: str) -> ParsedEvent: raw = normalize_ws(text) lowered = raw.lower() if any(phrase in lowered for phrase in ["fell asleep", "falling asleep", "asleep around", "finally asleep", "passed out", "went to sleep"]): return ParsedEvent("sleep", ACTION_DISPLAY["sleep"], raw) ``` All classification branches preserve `raw` as the notes value, including the fallback branch: ```python return ParsedEvent("note", ACTION_DISPLAY["note"], raw) ``` The untrusted notes value is then inserted into the CSV row: ```python return { "entry_id": next_entry_id(rows), "timestamp_utc": dt_utc.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), "date_local": dt_local.strftime("%Y-%m-%d"), "time_local": format_time_local(dt_local), "timezone": str(TIMEZONE), "action": event.action, "emoji": event.emoji, "notes": event.notes, ``` ### Technical Analysis The tracker accepts raw Discord message text, normalizes whitespace, and stores the resulting string in the `notes` column of `sleep_log.csv`. It does not neutralize values beginning with spreadsheet formula indicators such as `=`, `+`, `-`, or `@`. The Python CSV writer correctly escapes CSV syntax, but CSV quoting is not a defense against formula injection. When the resulting file is opened or imported into formula-capable spreadsheet software, a cell beginning with a dangerous prefix may be interpreted as a ...[truncated 1965 chars]
Remediation
View remediation
str: if not isinstance(value, str): return value candidate = value.lstrip() if candidate.startswith(("=", "+", "-", "@")): return "'" + value return value ``` Apply it before serialization: ```python def write_rows(rows: list[dict]) -> None: safe_rows = [ {key: sanitize_csv_cell(value) for key, value in row.items()} for row in rows ] with open(LOG_FILE, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=CSV_FIELDS) writer.writeheader() writer.writerows(safe_rows) ``` If exact raw-message preservation is required, store canonical records in JSON or another non-formula format and generate a separately sanitized CSV export for spreadsheet use. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes local scripts, relies on environment variables, and implies reading/writing tracker state, but it does not declare an explicit tool scope such as allowed tools or permissions. That creates an authorization ambiguity where an agent may execute file and environment-backed operations more broadly than reviewers or policy systems expect, increasing the chance of unintended file access or mutation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documented delete-latest command performs a destructive state change with no user-facing warning, confirmation step, or rollback guidance. In a conversational setting, ambiguous or accidental requests could cause silent loss of the most recent sleep record, which undermines integrity and may be hard for users to detect or recover from.

Content

No source excerpt is available for this finding.

Tainted flow: 'LOG_FILE' from os.environ.get (line 15, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/tracker.py (reported line 57)May include surrounding context.

python
def initialize_log() -> None:
    ensure_dirs()
    if not os.path.exists(LOG_FILE):
        with open(LOG_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=CSV_FIELDS)
            writer.writeheader()

Tainted flow: 'LOG_FILE' from os.environ.get (line 15, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/tracker.py (reported line 69)May include surrounding context.

python
def initialize_log() -> None:
    ensure_dirs()
    if not os.path.exists(LOG_FILE):
        with open(LOG_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=CSV_FIELDS)
            writer.writeheader()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code rewrites the sleep log CSV in place, and related commands can supersede or delete entries by changing persisted state. There are no confirmation prompts, user-facing log messages, or explanatory comments/docstrings in the file warning that commands will modify or remove persisted records.

Content

No source excerpt is available for this finding.

Tainted flow: 'STATE_FILE' from os.environ.get (line 16, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/tracker.py (reported line 226)May include surrounding context.

python
state["summary_message_id"] = summary_message_id
    if channel_id:
        state["channel_id"] = channel_id
    with open(STATE_FILE, "w", encoding="utf-8") as f:
        json.dump(state, f, indent=2)
        f.write("\n")
    return state

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This markdown file documents commands that log new sleep events, replace the latest entry, and delete the latest entry, but it does not include any warning that these actions modify or remove persisted user data in the workspace. Under the markdown-specific warning criterion, skills that affect user data should disclose that behavior so users understand the integrity impact.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The set_state function writes summary_message_id and channel_id to a persistent JSON file, but the script provides no warning, prompt, or explanatory documentation in this file that this metadata will be stored on disk. Because these values relate to external message/channel tracking, users should be clearly informed of the persistence behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.