Back to skill

Security audit

Investment Portfolio

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local portfolio tracker, but one calculator command can execute unintended shell commands from crafted input.

Review before installing. Use only trusted, numeric inputs with the dca command, avoid opening exported CSVs in spreadsheet software if tickers may be untrusted, and be aware that holdings are stored persistently in a home-directory folder and can be removed or overwritten without recovery.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/script.sh:316
Finding
Arbitrary Command Execution Through Bash Arithmetic Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/script.sh:316-331` **Vulnerability Type**: Bash arithmetic injection **Risk Level**: High ### Vulnerable Code ```bash cmd_dca() { local ticker="${1:?Usage: investment-portfolio dca <TICKER> <monthly_amount>}" local monthly="${2:?Missing monthly amount}" ticker=$(echo "$ticker" | tr 'a-z' 'A-Z') echo -e "${BOLD}DCA Calculator: $ticker @ \$$monthly/month${RESET}" echo "" echo " Month Investment Cumulative" echo " ──────────────────────────────────" local total=0 for m in $(seq 1 12); do total=$((total + monthly)) printf " %-7d \$%-12s \$%s\n" "$m" "$monthly" "$total" done echo "" echo " Total invested after 12 months: \$$total" } ``` ### Technical Analysis The `monthly` parameter comes directly from a command-line argument and is not validated before being referenced in Bash arithmetic expansion: ```bash total=$((total + monthly)) ``` Bash recursively interprets the value of a variable used in an arithmetic expression as another arithmetic expression. Arithmetic syntax can include array references whose subscripts undergo shell expansion. Consequently, a malicious value containing an array-subscript expression with command substitution can cause Bash to execute a command while evaluating `monthly`. Merely quoting the argument when invoking the script does not make the arithmetic evaluation safe. The dangerous interpretation happens later, at line 327, inside Bash's arithmetic evaluator. ### Attack Path 1. An attacker supplies or persuades a user or agent to supply a crafted value as the second argument to `dca`. 2. The value is stored unchanged in the `monthly` shell variable. 3. The loop reaches `total=$((total + monthly))`. 4. Bash recursively parses the value of `monthly` as an arithmetic expression. 5. A command substitution embedded in an arithmetic array subscript is evaluated by the shell. 6. The injected comman ...[truncated 876 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate the argument before it reaches any arithmetic context. If only whole-dollar amounts are supported, require an unsigned integer: ```bash [[ "$monthly" =~ ^[0-9]+$ ]] || die "Monthly amount must be a non-negative integer" ``` Apply a reasonable upper bound as well to prevent integer overflow or resource-related problems: ```bash (( monthly <= 1000000000 )) || die "Monthly amount is too large" ``` If decimal currency values must be supported, avoid Bash arithmetic and parse the value using Python's `decimal.Decimal` with a strict regular expression and explicit bounds. Pass the value through an environment variable or positional argument rather than interpolating it into generated Python or shell source. Recommended hardening steps: 1. Reject signs, whitespace, array syntax, operators, substitutions, and nonnumeric characters. 2. Define whether negative investments and decimal values are valid. 3. Enforce upper and lower bounds. 4. Add regression tests using arithmetic expressions, array references, command substitutions, empty input, large integers, and decimal input. 5. Never pass untrusted text into Bash arithmetic evaluation unless it has first been reduced to a canonical numeric representation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/script.sh:447
Finding
Spreadsheet Formula Injection in CSV Export<![CDATA[ ## Vulnerability Details **File Location**: `scripts/script.sh:447-456` **Vulnerability Type**: CSV/spreadsheet formula injection and improper CSV encoding **Risk Level**: Medium ### Vulnerable Code ```bash case "$fmt" in csv) echo "ticker,shares,buy_price,current_price,date" DB_FILE="$DB" python3 << 'PYEOF' import json, os with open(os.environ["DB_FILE"]) as f: for line in f: if not line.strip(): continue h = json.loads(line) print("{},{},{},{},{}".format(h["ticker"], h["shares"], h["buy_price"], h["current_price"], h.get("date",""))) PYEOF ;; ``` Ticker values originate from user-controlled input in the `add` command and are stored without a restrictive ticker-format validation: ```bash local ticker="${1:?Usage: investment-portfolio add <TICKER> <shares> <buy_price>}" ticker=$(echo "$ticker" | tr 'a-z' 'A-Z') ``` ### Technical Analysis The CSV exporter constructs rows with string formatting rather than a CSV encoder. The user-controlled `ticker` field is written directly to the first cell without: - CSV quoting or escaping; - rejection of commas, quotation marks, or line breaks; - neutralization of spreadsheet formula prefixes such as `=`, `+`, `-`, or `@`. Uppercasing the ticker does not remove these dangerous prefix characters. When the exported file is opened in spreadsheet software, a ticker beginning with a formula prefix may be interpreted as a formula instead of inert text. CSV structural characters can also alter the number of columns or create additional records, making the exported data ambiguous or misleading. ### Attack Path 1. An attacker causes a ticker beginning with `=`, `+`, `-`, or `@` to be added to the portfolio. 2. The `add` command stores that ticker in `holdings.jsonl`. 3. A user runs `scripts/script.sh export csv` and saves the output as a CSV file. 4. The exporter writes the ticker directly into the first cell without CSV escaping or formula neut ...[truncated 942 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use Python's standard `csv` module rather than manually joining values: ```python import csv import json import os import sys def spreadsheet_safe(value): text = str(value) if text.startswith(("=", "+", "-", "@")): text = "'" + text return text writer = csv.writer(sys.stdout, lineterminator="\n") writer.writerow(["ticker", "shares", "buy_price", "current_price", "date"]) with open(os.environ["DB_FILE"], encoding="utf-8") as f: for line in f: if not line.strip(): continue h = json.loads(line) writer.writerow([ spreadsheet_safe(h["ticker"]), h["shares"], h["buy_price"], h["current_price"], h.get("date", ""), ]) ``` Additional hardening should include: 1. Validate tickers when they are added, using an allowlist appropriate for supported assets. 2. Reject control characters, commas, quotation marks, and line breaks unless there is a documented need to support them. 3. Treat all textual CSV fields as potentially formula-bearing, including values beginning with whitespace followed by a formula marker. 4. Document whether the export is intended for machine processing or spreadsheet use. 5. Add tests covering formula prefixes, embedded commas, quotation marks, Unicode text, and line breaks. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
84% confidence
Finding
The skill describes commands that write portfolio data locally and may access environment configuration via PORTFOLIO_DIR, but the manifest does not declare any tool scope such as permissions or allowed-tools. This creates an authorization and transparency gap: an agent may invoke file-writing behavior without explicit user-visible restriction, increasing the chance of unintended local file modification or misuse if the implementation changes or is invoked in an unexpected context.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script automatically creates a persistent data directory under the user's home directory and stores portfolio holdings and transaction history there without any disclosure, consent, or retention controls. Because this data contains sensitive financial records, silent persistence can expose private information to other local users, backups, or later compromise of the host.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The remove command rewrites the portfolio database and permanently deletes matching entries with no confirmation, backup, or undo capability. In an agent-driven or automated context, a mistaken ticker, malformed prompt, or unintended invocation can silently destroy financial records and impair auditability.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The update command overwrites stored current_price values in place without warning, versioning, or backup. While less destructive than delete, accidental or maliciously induced updates can corrupt portfolio analytics and historical accuracy, especially because the tool also keeps transaction history separately but not prior record versions.

Static analysis

No suspicious patterns detected.