Back to skill

Security audit

scanner

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real stock scanner, but it uses risky dependency installation guidance and encourages recurring scheduled execution while reading local watchlists and writing reports.

Install only if you are comfortable with a local stock scanner reading your watchlist, sending ticker symbols to TradingView, and writing reports into your vault. Use a virtual environment instead of --break-system-packages, avoid sudo, review any cron entry before adding it, and treat imported watchlists as untrusted input, especially when generating Excel files.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:48
Finding
Unpinned and Inconsistent System-Wide Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:48`; related runtime instruction at `scanner.py:149-155` **Vulnerability Type**: Unpinned third-party dependencies, package mismatch, and unsafe modification of the system Python environment **Risk Level**: Medium ### Complete Code Snippet ```markdown 2. **Install dependencies**: `pip install tradingview_ta pandas numpy requests openpyxl --break-system-packages` ``` The runtime code instead requires a different package: ```python try: from tvDatafeed import TvDatafeed, Interval except ImportError: print(" ⚠️ tvDatafeed is not installed. Try: pip install tvDatafeed --break-system-packages") return None ``` ### Technical Analysis The installation command does not pin package versions or verify package integrity with hashes. It also uses `--break-system-packages`, which bypasses protections intended to prevent `pip` from modifying an externally managed system Python installation. There is a dependency mismatch: the documentation tells the user to install `tradingview_ta`, while the implementation imports `tvDatafeed`. This can cause failed execution and may lead users to install additional packages based only on an error message. Package names are security-sensitive, and installing an unexpected or similarly named package creates exposure to dependency confusion, typosquatting, and compromised upstream releases. Python packages can execute code during installation and whenever imported. Therefore, a malicious package would execute with the privileges of the account running `pip` or the scanner. ### Attack Path 1. An attacker publishes, compromises, or otherwise gains control of one of the unpinned packages or a similarly named package. 2. A user follows the Skill instructions and runs the provided `pip install` command. 3. `pip` retrieves the current package release without enforcing an audited version or hash. 4. Installation hooks or subsequently imported package code execute ...[truncated 993 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--break-system-packages` from all installation instructions. 2. Create and use a dedicated virtual environment: ```bash python3 -m venv .venv . .venv/bin/activate python -m pip install --require-hashes -r requirements.txt ``` 3. Supply a lock file containing exact, audited versions and cryptographic hashes. 4. Reconcile the dependency mismatch by documenting and installing the exact package imported by the implementation. 5. Remove unused dependencies such as `tradingview_ta` or `requests` if they are not required. 6. Document the verified source and expected version of `tvDatafeed`. 7. Run the scanner as a non-privileged user and explicitly warn users not to install its dependencies with `sudo`. 8. Add automated dependency scanning and periodically review pinned versions for known vulnerabilities. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scanner.py:958
Finding
Spreadsheet Formula Injection Through Watchlist Values<![CDATA[ ## Vulnerability Details **File Location**: Input source at `scanner.py:1093`; Excel export sinks at `scanner.py:958-977` **Vulnerability Type**: Spreadsheet formula injection **Risk Level**: Medium ### Complete Code Snippet The watchlist is read without validation or neutralization: ```python watchlist = pd.read_csv(csv_path) ``` Watchlist-derived strings are then included in the exported data frame and written to Excel: ```python df_all = pd.DataFrame(valid) for c in cols: if c not in df_all.columns: df_all[c] = None df_all = df_all[cols].sort_values("score", ascending=False) with pd.ExcelWriter(out_path, engine="openpyxl") as writer: df_all.to_excel(writer, sheet_name="📊全部标的", index=False) for name, col, val in [ ("🏅高分≥60", "score", lambda x: x >= 60), ("⭐多指标共振", "multi_buy", lambda x: x == True), ("🚀放量突破", "volume_breakout", lambda x: x == True), ("🏆历史新高", "new_high", lambda x: x == True), ("🔵RSI回升", "rsi_recover", lambda x: x == True), ("⚠️量价背离", "price_vol_divergence", lambda x: x == True), ]: subset = df_all[df_all[col].apply(val)] if not subset.empty: subset.to_excel(writer, sheet_name=name, index=False) ``` ### Technical Analysis The CSV fields `ticker`, `name`, `market`, and `sector` are user-controlled and are propagated into the result dictionaries. They are subsequently written to XLSX cells without first neutralizing formula-like values. In particular, a string beginning with `=` can be serialized as a spreadsheet formula rather than inert text. Values beginning with `+`, `-`, or `@` should also be treated as dangerous for compatibility with spreadsheet applications and CSV/formula-injection defenses. The vulnerability is triggered when the generated workbook is opened in software that evaluates the malicious cell. Depending on application policies, formulas can create external references, deceptive hyperlinks, data-disclosur ...[truncated 1382 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sanitize every watchlist-derived string before Excel export. 2. Prefix strings beginning with `=`, `+`, `-`, or `@` with an apostrophe so spreadsheet applications treat them as text. 3. Remove leading control characters, tabs, carriage returns, and line feeds before checking the first visible character. 4. Apply neutralization to all string columns rather than only the currently known watchlist fields. 5. Consider writing explicit text cells with `openpyxl` and setting their number format to text. 6. Keep the unsanitized values only in memory for ticker lookup if required; export a separately sanitized representation. 7. Add regression tests for payloads beginning with each dangerous character and for payloads preceded by whitespace or control characters. 8. Document that imported watchlists are untrusted input. Example defensive helper: ```python def excel_safe(value): if not isinstance(value, str): return value cleaned = value.replace("\r", " ").replace("\n", " ") if cleaned.lstrip().startswith(("=", "+", "-", "@")): return "'" + cleaned return cleaned for column in df_all.select_dtypes(include=["object"]).columns: df_all[column] = df_all[column].map(excel_safe) ``` ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scanner.py:742
Finding
Markdown and Raw HTML Injection Through Unescaped Watchlist Fields<![CDATA[ ## Vulnerability Details **File Location**: `scanner.py:742-756`; repeated output sinks at `scanner.py:778`, `scanner.py:798`, and `scanner.py:881` **Vulnerability Type**: Untrusted Markdown content injection **Risk Level**: Low ### Complete Code Snippet ```python rows = [header, divider] for r in sorted(items, key=lambda x: (-x.get("score", 0), x["market"], x["ticker"])): mkt = MARKET_EMOJI.get(r["market"], "") + " " + r["market"] line = (f"| {mkt} | `{r['ticker']}` | {r['name']} | {r['sector']} | " f"{r['close']:,.3f} | {fmt_pct(r['pct_chg'])} | " f"{r['ma_short']:,.3f} | {r['ma_long']:,.3f} | " f"{r['rsi']:.1f} | {r.get('score', 0)}") for _, key_or_fn in extras: val = key_or_fn(r) if callable(key_or_fn) else r.get(key_or_fn, "—") line += f" | {val}" line += f" | {r['data_date']} |" rows.append(line) return rows + [""] ``` The same unescaped fields are inserted into several other report sections: ```python f"| {mkt} | `{r['ticker']}` | {r['name']} | {r['sector']} | " ``` ### Technical Analysis The report generator directly interpolates the CSV-derived `ticker`, `name`, `market`, and `sector` values into Markdown. It does not escape pipe characters, backticks, brackets, line breaks, or raw HTML. An attacker-controlled field can therefore terminate a table cell, create additional rows or headings, insert links and images, or inject raw HTML supported by the Markdown renderer. In Obsidian or another Markdown viewer, this could visually alter the report or load attacker-controlled external resources. The report may also be presented to an AI Agent. If generated content is later treated as trusted instructions rather than untrusted data, an attacker can place instruction-like text into the report and attempt indirect prompt injection. The current scanner does not itself execute such instructions, so this is a downstream trust-boundary risk rather than direct code execution. ...[truncated 1331 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape Markdown control characters in all watchlist-derived values. 2. Replace carriage returns and line feeds with spaces before rendering table cells. 3. Escape table delimiters such as `|` and backticks used around ticker values. 4. Encode or strip raw HTML if HTML rendering is not required. 5. Validate ticker and market values against restrictive allowlists. 6. Consider rendering watchlist fields as HTML-escaped text rather than raw Markdown. 7. Treat generated reports as untrusted data when presenting them to an AI Agent; never interpret report text as executable instructions. 8. Add tests for pipes, backticks, headings, links, images, raw HTML, and multiline values. Example table-cell sanitizer: ```python import html def markdown_table_safe(value): text = str(value) text = text.replace("\r", " ").replace("\n", " ") text = html.escape(text, quote=True) text = text.replace("\\", "\\\\") text = text.replace("|", "\\|") text = text.replace("`", "\\`") return text ``` Apply this function to `ticker`, `name`, `market`, `sector`, error messages, and any other text derived from external input before constructing the Markdown output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill clearly performs filesystem reads and writes against the user's Obsidian vault, but it does not declare an explicit tool scope or permissions boundary. That creates a trust and review gap: an agent may invoke file-capable tools without the skill clearly constraining which paths are intended, increasing the chance of unintended file access or modification.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger list ends with broad language such as applying whenever the user asks for batch technical analysis or mentions TradingView scanning scenarios, which expands activation beyond a tight phrase set. Overbroad triggers can cause the skill to run in contexts the user did not clearly intend, leading to unexpected filesystem access, package installation, or network activity.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill instructs reading a watchlist from the user's vault and writing generated reports back into the vault, but it does not present a clear warning about local filesystem side effects at the point of use. This can surprise users and increase the risk of modifying sensitive notes or writing outputs into unintended locations if paths are misconfigured.

Session Persistence

Medium
Category
Rogue Agent
Content
### 方案 A:用户本地 cron(推荐)
告诉用户在本机设置:
```bash
# crontab -e
0 18 * * 1-5 cd /path/to/vault && python /path/to/scanner.py --csv watchlist.csv --output 技术指标扫描/
```
Confidence
88% confidence
Finding
The skill recommends installing a persistent cron job on the user's machine to execute the scanner automatically. Persistence itself is sensitive because it causes recurring code execution and ongoing file/network activity outside the immediate user session, which can continue after the user forgets it was configured.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Natural-language strings in the banner, help text, runtime messages, and generated report are predominantly Chinese, effectively imposing a specific language on users. The file does not offer opt-in language selection or explain that the skill is intentionally region/language-specific.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
This markdown file contains a full report in a single forced language/locale, but does not mention that the user opted into Chinese output or that the skill is intentionally region-specific. The policy requires either offering language choice or clearly documenting a justified locale constraint.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The code sends watchlist-derived ticker symbols to TradingView via tvDatafeed, which is external network transmission of user-sourced portfolio metadata. While tickers are not highly sensitive by themselves, a watchlist can reveal investment interests or strategy, and the skill description says the source data comes from an Obsidian vault, so exfiltration to a third party should be explicitly disclosed and consented to.

Static analysis

No suspicious patterns detected.