Back to skill

Security audit

Trading Journal

Security checks for vulnerabilities and agentic risk

Overview

This local trading-journal skill is coherent, but it has a real file-containment bug and unsafe CSV export behavior that users should review before installing.

Use caution before installing. The skill appears local-only and purpose-aligned, but it should validate journal dates to strict YYYY-MM-DD paths and sanitize CSV cells before opening exports in spreadsheet software. Treat trade notes or imported trade data from others as untrusted.

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/trade_logger.py:33
Finding
Path Traversal Through Unvalidated Journal Dates<![CDATA[ ## Vulnerability Details **File Location**: `scripts/trade_logger.py:33-36`, with attacker-controlled data reaching the vulnerable function at `scripts/trade_logger.py:92-101` **Vulnerability Type**: Path traversal and arbitrary JSON file overwrite **Risk Level**: High ### Vulnerable Code ```python def _journal_path(target_date: str = None) -> str: """Get path for a date's journal file.""" if target_date is None: target_date = date.today().isoformat() return os.path.join(JOURNALS_DIR, target_date + ".json") ``` The `date` field is passed into this path construction without validation: ```python def add_trade(trade_data: dict) -> dict: """Add a new trade entry and calculate P&L.""" target_date = trade_data.get("date", date.today().isoformat()) # Auto-calculate P&L if not trade_data.get("open", False) and trade_data.get("exit_price") is not None: trade_data["pnl"] = _calculate_pnl(trade_data) trades = _load_trades(target_date) trades.append(trade_data) _save_trades(trades, target_date) ``` ### Technical Analysis `_journal_path()` concatenates an untrusted date value with `.json` and passes it directly to `os.path.join()`. It does not require the value to be a canonical ISO date, reject path separators, normalize the result, or verify that the resolved path remains beneath `JOURNALS_DIR`. A value containing parent-directory components, such as `../backup`, produces a path outside the intended journal directory. A value beginning with an absolute path can also cause `os.path.join()` to discard the intended base directory. The vulnerable function is used by both `_load_trades()` and `_save_trades()`. Consequently, the issue affects the `add`, `update`, and `delete` workflows. The target is constrained to a filename ending in `.json`, and existing files must contain the expected JSON object structure for mutation operations to complete successfully. ### Attack Path 1. An attacker supplies a ...[truncated 1248 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every journal date with `date.fromisoformat()` before using it in a filename. 2. Require canonical `YYYY-MM-DD` formatting by comparing the parsed date's `isoformat()` result with the original input. 3. Reject values containing path separators, parent-directory components, null bytes, or absolute paths. 4. Resolve the final path and verify containment beneath the resolved journal directory. 5. Apply the same centralized validation to `add`, `list`, `update`, and `delete`. 6. Consider opening files through a directory file descriptor or another platform-appropriate safe-path mechanism to reduce symlink race risks. 7. Add tests covering `../target`, absolute paths, malformed dates, encoded separators, and symlinks. Example hardening: ```python def _journal_path(target_date: str = None) -> str: value = target_date or date.today().isoformat() try: parsed = date.fromisoformat(value) except (TypeError, ValueError): raise ValueError("Journal date must use YYYY-MM-DD format") if parsed.isoformat() != value: raise ValueError("Journal date must be canonical YYYY-MM-DD") base = os.path.realpath(JOURNALS_DIR) candidate = os.path.realpath(os.path.join(base, value + ".json")) if os.path.commonpath([base, candidate]) != base: raise ValueError("Journal path escapes the journal directory") return candidate ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/journal_export.py:121
Finding
CSV Formula Injection in Journal Exports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/journal_export.py:121-142` **Vulnerability Type**: CSV formula injection **Risk Level**: Medium ### Vulnerable Code ```python def _export_csv(trades: list, output_path: str): """Export to CSV format.""" fieldnames = ["date", "symbol", "type", "direction", "entry_price", "exit_price", "quantity", "multiplier", "entry_time", "exit_time", "fees", "stamp_duty", "strategy", "notes", "tags", "open", "pnl"] with open(output_path, "w", encoding="utf-8-sig", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore") writer.writeheader() for t in trades: row = {} row["date"] = t.get("_date", t.get("date", "")) for fn in fieldnames: if fn == "date": continue val = t.get(fn, "") if isinstance(val, list): val = "; ".join(str(v) for v in val) row[fn] = val writer.writerow(row) print(f"Exported to {output_path}") ``` ### Technical Analysis Trade fields such as `symbol`, `strategy`, `notes`, and `tags` can contain user-controlled strings. These values are written directly to CSV cells without neutralizing formula indicators. Quoting performed by Python's `csv` module protects the CSV structure but does not prevent spreadsheet applications from interpreting a cell beginning with `=`, `+`, `-`, or `@` as a formula. Some applications may also ignore leading whitespace, tabs, or carriage returns before a formula marker. The exact consequences depend on the spreadsheet application and its security configuration. Potential behaviors include external network requests, disclosure of spreadsheet data, deceptive hyperlinks, or execution through legacy features such as DDE where those features remain enabled. ### Attack Path 1. An attacker provides a trade fie ...[truncated 1115 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every string-valued CSV cell as untrusted. 2. Before export, detect values whose first significant character is `=`, `+`, `-`, or `@`. 3. Also account for leading tabs, carriage returns, newlines, and whitespace that spreadsheet applications may ignore. 4. Neutralize dangerous cells by prefixing them with a single quote or another format-appropriate text marker. 5. Apply sanitization after list values have been joined and before passing the row to `writer.writerow()`. 6. Document that CSV exports contain untrusted user content and should be opened with external links and macros disabled. 7. Add tests for direct and whitespace-prefixed formulas in every text field. Example hardening: ```python def _safe_csv_cell(value): if not isinstance(value, str): return value probe = value.lstrip(" \t\r\n") if probe.startswith(("=", "+", "-", "@")): return "'" + value return value ``` Apply it during row construction: ```python if isinstance(val, list): val = "; ".join(str(v) for v in val) row[fn] = _safe_csv_cell(val) ``` ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The usage guide specifies activation examples and agent responses entirely in Chinese, such as "记一笔交易" and the sample report/output, while the skill does not state that language selection is optional. This can violate language/locale policy because it implicitly constrains interaction to Chinese without user opt-in or a documented justification.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code emits user-facing content in Chinese throughout the generated Markdown report, including the title, labels, and status text, and the header itself presents a bilingual title without any option to select output language. That creates a locale constraint in natural-language behavior without user opt-in, which matches the language-policy violation category.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The human-readable report strings are all hard-coded in Chinese, and the module heading also mixes in Chinese labeling. This creates a language/locale policy issue because users are not given an opt-in or alternative output language, and the file does not state that the tool is intentionally limited to a Chinese-speaking context.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
This markdown reference presents user-facing content in both English and Chinese, including the title and report templates, but does not state whether the bilingual output is optional or user-selectable. Under the language/locale policy rule, forcing or assuming a specific language without opt-in can be a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
This markdown reference includes substantial Chinese text and bilingual labels throughout, but it does not explain whether Chinese output is required, optional, or user-selectable. Under the language/locale policy criterion, forcing or implicitly defaulting to a specific language without opt-in can be a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
Line L002 presents the script title partly in Chinese while the rest of the usage/help text is in English. This creates a language policy concern because the file imposes multilingual output conventions without indicating user opt-in or that the skill is intended for a specific locale.

Static analysis

No suspicious patterns detected.