Back to skill

Security audit

Mood Tracker

Security checks for vulnerabilities and agentic risk

Overview

This mood-tracking skill is purpose-aligned, but its bundled script has verified command-injection risks that could let crafted mood inputs run arbitrary local commands.

Review this skill carefully before installing. It stores emotional wellbeing and journal data locally in plaintext, and the current script should be fixed before use because crafted mood, note, history, or trigger inputs could execute local commands under your account.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/mood_tracker.sh:31
Finding
Python Code Injection Through Mood Score and Note Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mood_tracker.sh`, lines 31-43 **Vulnerability Type**: Python source-code injection through unsafe shell-variable interpolation **Risk Level**: High ### Vulnerable Code ```bash log) score="${1:-3}"; note="${2:-}" python3 << PYEOF import json, time with open("$DB") as f: data = json.load(f) emojis = {1:"😢",2:"😕",3:"😐",4:"🙂",5:"😄"} labels = {1:"Terrible",2:"Not Great",3:"Okay",4:"Good",5:"Amazing"} data.append({"score":int("$score"),"emoji":emojis.get(int("$score"),"😐"), "label":labels.get(int("$score"),"?"),"note":"$note", "date":time.strftime("%Y-%m-%d"),"time":time.strftime("%H:%M"), "weekday":time.strftime("%A")}) with open("$DB","w") as f: json.dump(data, f, indent=2) print("{} Mood: {} ({}/5) {}".format(emojis.get(int("$score")), labels.get(int("$score")), "$score", "$note")) PYEOF ;; ``` ### Technical Analysis The unquoted heredoc delimiter permits Bash to expand `$score`, `$note`, and `$DB` before Python parses the generated program. Consequently, the command arguments are treated as Python source code rather than inert data. A crafted score can escape an `int("...")` expression and introduce another Python expression. A crafted note can terminate or extend its surrounding string expression. For example, a note shaped as a concatenated Python expression can cause `__import__("os").system(...)` to be evaluated while Python constructs the dictionary or output string. The script performs no Bash-level validation that the score is an integer from 1 through 5. Python conversion with `int()` does not provide injection protection because the attacker-controlled value has already been inserted into the source code before `int()` executes. ### Attack Path 1. An attacker supplies or persuades a user or Agent to supply a specially crafted `score` or `note` argument to the `log` command. 2. Bash expands the argument inside the unquoted Python here ...[truncated 869 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pass user input as arguments rather than interpolating it into Python source. Use a single-quoted heredoc delimiter so Bash performs no expansion: ```bash score="${1:-3}" note="${2:-}" case "$score" in 1|2|3|4|5) ;; *) echo "Score must be an integer from 1 through 5" >&2; exit 1 ;; esac python3 - "$DB" "$score" "$note" <<'PYEOF' import json import sys import time db, score_text, note = sys.argv[1:] score = int(score_text) with open(db, encoding="utf-8") as f: data = json.load(f) emojis = {1: "😢", 2: "😕", 3: "😐", 4: "🙂", 5: "😄"} labels = {1: "Terrible", 2: "Not Great", 3: "Okay", 4: "Good", 5: "Amazing"} data.append({ "score": score, "emoji": emojis[score], "label": labels[score], "note": note, "date": time.strftime("%Y-%m-%d"), "time": time.strftime("%H:%M"), "weekday": time.strftime("%A"), }) with open(db, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) PYEOF ``` Apply the same data/source separation to every embedded Python block. Add automated tests using quotes, backslashes, newlines, and Python-like expressions in notes to verify that they remain plain data. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/mood_tracker.sh:88
Finding
Python Code Injection Through History Count Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mood_tracker.sh`, lines 88-94 **Vulnerability Type**: Python source-code injection through unsafe numeric argument interpolation **Risk Level**: High ### Vulnerable Code ```bash history) n="${1:-14}" python3 << PYEOF import json with open("$DB") as f: data = json.load(f) print("📋 Mood History:") for d in data[-int("$n"):][::-1]: print(" {} {} {} {}/5 {}".format(d["date"], d["time"], d["emoji"], d["score"], d.get("note",""))) PYEOF ;; ``` ### Technical Analysis The history count is inserted directly into an unquoted Python heredoc: ```python int("$n") ``` Because Bash expands `$n` before Python parses the program, a value containing quotes and Python operators can escape the intended string literal. The attacker can construct an expression that remains valid inside `int(...)` while evaluating an injected function call as a side effect. Treating the value with `int()` is not a security boundary. The injection occurs during Python parsing, before the conversion validates the resulting expression's value. ### Attack Path 1. An attacker provides a crafted argument to `history`. 2. Bash substitutes that argument into the `int("$n")` expression. 3. The substituted text terminates the intended string and adds a Python expression. 4. Python evaluates the expression while calculating the slice boundary. 5. The expression can call `__import__("os").system(...)` or another execution primitive. 6. The selected command runs with the permissions of the user invoking the script. A benign proof of concept can use an injected expression that creates a marker file and then resolves to a valid integer so the surrounding slice remains syntactically and semantically valid. ### Impact Assessment The vulnerability allows arbitrary Python and operating-system command execution as the invoking user. It can expose or modify any user-accessible data, including mood history and journal files. It can als ...[truncated 165 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate the count as a bounded decimal integer and pass it to Python as an argument: ```bash n="${1:-14}" case "$n" in ''|*[!0-9]*) echo "History count must be a positive integer" >&2 exit 1 ;; esac if (( n < 1 || n > 1000 )); then echo "History count must be between 1 and 1000" >&2 exit 1 fi python3 - "$DB" "$n" <<'PYEOF' import json import sys db = sys.argv[1] count = int(sys.argv[2]) with open(db, encoding="utf-8") as f: data = json.load(f) print("📋 Mood History:") for entry in data[-count:][::-1]: print(" {} {} {} {}/5 {}".format( entry["date"], entry["time"], entry["emoji"], entry["score"], entry.get("note", ""), )) PYEOF ``` The quoted heredoc prevents shell expansion, while positional arguments ensure that values are parsed as data. The upper bound also prevents unexpectedly expensive output from excessive counts. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/mood_tracker.sh:132
Finding
Python Code Injection Through Trigger Mood Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mood_tracker.sh`, lines 132-145 **Vulnerability Type**: Python source-code injection through dynamically constructed `python3 -c` program **Risk Level**: High ### Vulnerable Code ```bash triggers) mood="${1:-}" python3 -c " import json from collections import Counter with open('$DB') as f: data = json.load(f) target = int('$mood') if '$mood' else None filtered = [d for d in data if d.get('note') and (target is None or d['score'] == target)] notes = Counter(d['note'] for d in filtered) label = 'Mood {}'.format(target) if target else 'All moods' print('🔍 Common triggers ({}):'.format(label)) for note, count in notes.most_common(10): print(' {} ({})'.format(note, count)) ";; ``` ### Technical Analysis The script constructs an entire Python program as a double-quoted shell string. Bash expands `$mood` into multiple single-quoted Python literals: ```python target = int('$mood') if '$mood' else None ``` A mood argument containing a single quote and Python operators can break out of these literals. Because the value appears in both the conditional expression and its condition, a payload can be designed to evaluate an injected function call and then return a numeric string acceptable to `int()`. The same construction also interpolates `$DB` into Python source. Although the default database path is benign, this pattern is unsafe because `MOOD_DIR` can influence the path. All variable values should be passed separately from executable source. ### Attack Path 1. An attacker supplies a crafted mood filter to the `triggers` command. 2. Bash expands the value into the double-quoted `python3 -c` program. 3. Embedded single quotes alter the generated Python expression. 4. Python parses the attacker-controlled expression as executable code. 5. During evaluation of the conditional expression, the payload invokes a Python or operating-system execution primitive. 6. The payload runs with the privi ...[truncated 664 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not generate Python with `python3 -c` and interpolated shell variables. Validate the optional filter and pass both the database path and filter as positional arguments: ```bash mood="${1:-}" if [[ -n "$mood" ]]; then case "$mood" in 1|2|3|4|5) ;; *) echo "Mood must be an integer from 1 through 5" >&2; exit 1 ;; esac fi python3 - "$DB" "$mood" <<'PYEOF' import json import sys from collections import Counter db = sys.argv[1] mood_text = sys.argv[2] target = int(mood_text) if mood_text else None with open(db, encoding="utf-8") as f: data = json.load(f) filtered = [ entry for entry in data if entry.get("note") and (target is None or entry["score"] == target) ] notes = Counter(entry["note"] for entry in filtered) label = "Mood {}".format(target) if target is not None else "All moods" print("🔍 Common triggers ({}):".format(label)) for note, count in notes.most_common(10): print(" {} ({})".format(note, count)) PYEOF ``` Use this argument-passing pattern consistently for database paths and all other values. Add regression tests containing single quotes, double quotes, shell metacharacters, Python operators, and newlines. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill metadata declares no explicit tool scope or permissions, yet the skill is described as storing data locally, which implies file write capability. In an agent environment, undeclared write access weakens least-privilege controls and can allow the skill to modify local files without clear user or platform visibility.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The description says 'Use when you need Mood Tracker capabilities,' which is overly broad and can cause the agent to invoke this skill in loosely related personal, wellness, journaling, or organization contexts. Overbroad invocation increases the chance of unnecessary access to sensitive emotional data and unintended file-writing behavior in conversations that did not clearly request mood tracking.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script persistently stores highly sensitive mental-health data, including mood scores, notes, dates, and times, in predictable local files under the user's home directory without any disclosure, consent prompt, or privacy controls. In a personal wellbeing skill, this is especially sensitive because users may reasonably provide intimate emotional information without realizing it will remain on disk and be accessible to other local users, backups, or malware.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The journal command appends arbitrary free-text entries to a dated markdown file without clearly informing the user that their personal reflections are being permanently stored. Because journal text can contain deeply sensitive health, relationship, or crisis-related information, undisclosed local persistence creates a privacy risk and increases exposure through shared machines, backups, or endpoint compromise.

Static analysis

No suspicious patterns detected.