Back to skill

Security audit

Stock Valuation

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible stock-report generator, but its report-building code has review-worthy flaws that could expose local files or unsafe web-sourced content in generated reports.

Review before installing. Use this only in a constrained environment with no sensitive local files or credentials available to the agent, avoid untrusted input JSON, and treat generated reports as informational rather than financial advice. Safer installation would require pinned dependencies, private per-run temp directories, path validation for chart files, HTML escaping or templating with autoescape, and JavaScript disabled during PDF rendering.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_report.py:53
Finding
Unrestricted Chart Paths Allow Arbitrary Local File Disclosure Through Base64 Embedding<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_report.py:53-55` and `scripts/generate_report.py:419-421` **Vulnerability Type**: Arbitrary local file read and unintended data embedding **Risk Level**: High ### Vulnerable Code ```python def b64img(path): if not path or not os.path.isfile(path): return "" try: with open(path, "rb") as f: return f"data:image/png;base64,{base64.b64encode(f.read()).decode()}" except: return "" ``` The paths passed to this function are obtained directly from the input JSON: ```python charts = data.get("charts", {}) ``` They are then embedded in the generated report: ```python chart_imgs = [] for key in ["price", "revenue", "margins", "pe"]: src = b64img(charts.get(key, "")) if src: chart_imgs.append(f'<img src="{src}" alt="{key}">') ``` ### Technical Analysis Embedding generated charts as Base64 is consistent with the declared requirement to produce portable HTML reports. The encoding itself is therefore not evidence of a covert channel. However, `b64img()` accepts any regular file path supplied through the data JSON. It does not verify that the file: - Is located in the expected chart-output directory. - Has a permitted filename. - Is a genuine PNG image. - Was created by the current pipeline execution. - Is not a symbolic link. - Falls below a safe size limit. The `data:image/png` prefix does not validate or transform the underlying content. Any readable file is loaded in full, Base64-encoded, and written into the HTML source. This exceeds the minimum filesystem access required to embed the four charts. Because `generate_report.py` accepts an arbitrary JSON file as a positional argument, an attacker who can influence that file can make the process read files available under the agent's operating-system privileges. ### Attack Path 1. The attacker supplies or modifies a pipeline JSON document. 2. A chart entry is changed to reference a sensitive local file: ```json { ...[truncated 1441 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not accept chart paths from the input JSON. Reconstruct expected chart filenames from a strictly validated ticker and a pipeline-owned working directory. - Resolve and validate every path before opening it: ```python from pathlib import Path CHART_DIR = Path("/tmp/stock-valuation/charts").resolve() def validated_chart_path(raw_path): candidate = Path(raw_path) if candidate.is_symlink(): raise ValueError("Symbolic links are not allowed") resolved = candidate.resolve(strict=True) if CHART_DIR not in resolved.parents: raise ValueError("Chart path is outside the permitted directory") if resolved.suffix.lower() != ".png": raise ValueError("Only PNG charts are permitted") return resolved ``` - Open files using protections against symbolic-link traversal where supported, such as `os.open()` with `O_NOFOLLOW`. - Verify the PNG signature and decode the image with a trusted image library before embedding it. - Enforce a conservative maximum file size before reading. - Create a unique per-run private directory with mode `0700`, and accept only files created in that directory. - Avoid broad exception handling that silently hides attempted invalid file access. Log validation failures without exposing sensitive paths. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_report.py:534
Finding
Unescaped Research Data Enables Stored HTML and JavaScript Injection in Generated Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_report.py:534-540` **Vulnerability Type**: Stored HTML injection / local report cross-site scripting **Risk Level**: High ### Vulnerable Code ```python # Seeking Alpha Research (from research) sa = research.get("sa_articles", []) if sa: sa_cards = "" for a in sa: rating = a.get("rating", "") sc = "bul" if "buy" in rating.lower() else ("ber" if "sell" in rating.lower() else "net") sa_cards += f'<div class="tw"><span class="au">"{a.get("title","")}"</span> · {a.get("date","")} · <span class="st {sc}">{rating}</span><p style="margin-top:8px; color:#475569">{a.get("summary","")}</p></div>\n' ``` The same unsafe interpolation pattern appears in other report sections, including: ```python tw_cards += f'<div class="tw"><span class="au">@{t.get("user","")}</span> · <span class="st {sc}">{stance}</span><p style="margin-top:8px; color:#475569">{t.get("summary","")}</p></div>\n' ``` ```python items = "\n".join(f"<li>{c}</li>" for c in cats) ``` ```python items = "\n".join(f"<li>{r}</li>" for r in risks) ``` ### Technical Analysis Research JSON fields are inserted directly into an HTML document without contextual output encoding or sanitization. The affected values include article titles, ratings, summaries, social-media usernames and posts, catalysts, risks, company names, and other pipeline fields. These inputs are not inherently trusted. The Skill instructions require the agent to collect them from web searches, Seeking Alpha, X/Twitter, and fetched earnings pages. Content originating on external websites can contain attacker-controlled markup or text deliberately designed to survive extraction. An injected closing tag can break out of the intended element and introduce arbitrary HTML or JavaScript. Because the report is subsequently opened in a browser or headless Chrome for PDF generation, active content may execute in the browser context. Example maliciou ...[truncated 1996 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Escape every untrusted value according to its HTML context before interpolation: ```python from html import escape title = escape(str(a.get("title", "")), quote=True) date = escape(str(a.get("date", "")), quote=True) rating_text = escape(str(rating), quote=True) summary = escape(str(a.get("summary", "")), quote=True) ``` - Apply the same encoding to all values from the pipeline and research JSON, including ticker, company name, tweets, catalysts, risks, geographic data, and revenue-composition fields. - Prefer a template engine with automatic HTML escaping, such as Jinja2 with autoescape enabled, rather than manual string concatenation. - If limited formatting is intentionally supported, sanitize it with a strict allowlist that excludes scripts, event-handler attributes, iframes, forms, and unsafe URL schemes. - Add a restrictive Content Security Policy to the generated document, for example: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline';"> ``` - Disable JavaScript during headless PDF generation where operationally possible. - Validate field types and impose length limits to reduce malformed-document and denial-of-service risks. - Add regression tests using payloads containing `<script>`, event handlers, closing tags, quotes, and `javascript:` URLs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_charts.py:59
Finding
Predictable Shared Temporary Files Permit Symlink and Race-Condition Attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_charts.py:59-62` **Vulnerability Type**: Unsafe predictable temporary-file handling **Risk Level**: Medium ### Vulnerable Code ```python fig.tight_layout() path = f"/tmp/{ticker}_chart_price.png" fig.savefig(path, dpi=150) plt.close(fig) charts.append({"type": "price_sma", "path": path}) ``` The same predictable naming pattern is used for the other charts: ```python path = f"/tmp/{ticker}_chart_revenue.png" path = f"/tmp/{ticker}_chart_margins.png" path = f"/tmp/{ticker}_chart_pe.png" ``` Related pipeline and report artifacts also use predictable shared paths: ```python output_path = args.output or f"/tmp/{ticker}_data.json" ``` ```python out = args.output or f"/tmp/{ticker}_report.html" ``` ### Technical Analysis The Skill writes files into the globally shared `/tmp` directory using names derived only from a public stock ticker. It does not create a private per-run directory, use exclusive file creation, reject symbolic links, or atomically publish completed files. On a multi-user system, another local account can predict names such as `/tmp/AAPL_chart_price.png` before the Skill runs. Depending on platform protections and library behavior, a pre-created symbolic link may redirect writes to another file writable by the victim account. There is also a time-of-check/time-of-use window between chart creation and report generation. An attacker can replace a chart file after it is generated but before `b64img()` reads it. Combined with the unrestricted file embedding issue, this can cause local file disclosure. Concurrent reports for the same ticker can also overwrite one another and produce inconsistent reports. ### Attack Path 1. The attacker observes or predicts that the victim will generate a report for `AAPL`. 2. The attacker pre-creates `/tmp/AAPL_chart_price.png` as a symbolic link to a victim-writable target, or waits until chart generation completes. 3. When `fig.savefig() ...[truncated 1102 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a unique private directory for every pipeline run: ```python import tempfile from pathlib import Path run_dir = Path(tempfile.mkdtemp(prefix="stock-valuation-")) run_dir.chmod(0o700) path = run_dir / "chart_price.png" ``` - Pass the private run directory explicitly between pipeline components rather than reconstructing shared filenames. - Use exclusive creation and `O_NOFOLLOW` where files must be opened directly. - Write outputs to a temporary file in the same private directory and atomically rename them after completion. - Validate tickers against a narrow allowlist such as `^[A-Z0-9.^-]{1,15}$`. - Set restrictive file permissions, such as `0600`, for JSON and HTML artifacts. - Clean up private run directories after successful delivery, subject to operational retention requirements. - Do not trust an existing file merely because its path matches an expected chart filename. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:19
Finding
Runtime Dependencies Are Installed Without Version Pinning or Integrity Locking<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:19-20` **Vulnerability Type**: Unpinned runtime dependency retrieval **Risk Level**: Medium ### Vulnerable Code ```bash uv run --with yfinance,matplotlib,lxml python3 $SKILL_DIR/scripts/run_pipeline.py TICKER ``` The documentation also invokes report generation without a locked environment: ```bash uv run python3 $SKILL_DIR/scripts/generate_report.py /tmp/TICKER_data.json --research /tmp/TICKER_research.json ``` ### Technical Analysis The Skill directs the agent to resolve and install `yfinance`, `matplotlib`, and `lxml` dynamically by package name, without exact versions, hashes, a lock file, or an explicitly trusted package index. This means the code executed during a future Skill run is not limited to what was reviewed in the Skill package. Dependency behavior can change after the audit because of: - A compromised upstream release. - A compromised package-index or maintainer account. - An unexpected incompatible release. - Dependency-resolution changes in transitive packages. - Use of an untrusted or attacker-controlled package index in local `uv` configuration. Python packages may execute code during installation, import, or ordinary runtime. The scripts immediately import these dependencies, so a compromised resolved package would execute with all privileges available to the agent process. No evidence in the reviewed project shows intentional dependency confusion or a typosquatted package. The issue is the absence of reproducible dependency controls. ### Attack Path 1. A dependency or transitive dependency publishes a compromised version, or the runtime is configured to use an attacker-controlled index. 2. A later agent run executes the documented `uv run --with ...` command. 3. `uv` resolves the current available package versions rather than an audited locked set. 4. The compromised package is installed and imported by the pipeline scripts. 5. Malicious package code executes under ...[truncated 698 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Define exact direct and transitive dependency versions in a committed lock file. - Use `uv sync --locked` or an equivalent command that refuses unreviewed resolution changes. - Record and verify package hashes where supported. - Configure a specific trusted package index rather than inheriting arbitrary environment configuration. - Periodically update dependencies through a reviewed process with automated vulnerability scanning. - Separate dependency installation from normal Skill execution so network-based package resolution does not occur on every run. - Run the pipeline in a sandbox with minimal filesystem access, no unnecessary credentials, and restricted outbound network access. - Maintain a software bill of materials for the resolved environment. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a broad valuation-report pipeline with quantitative collection, qualitative web research, and final polished HTML/PDF output. The actual code chunk only accepts a ticker, fetches company metadata from yfinance, selects peers from predefined mappings or industry matching, enriches those peers with basic market data, and prints JSON. While peer detection could be a supporting substep in a valuation workflow, the description does not mention this utility, and the code does not implement the core promised behaviors of report generation, web research, or valuation analysis. Therefore this chunk's primary purpose is materially narrower and different from the declared skill behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
There is a clear description-behavior mismatch. The declared purpose describes an end-to-end valuation reporting pipeline with data collection, external research, and polished report generation. The actual code only fetches earnings calendar information for a ticker using yfinance and returns structured JSON. Its primary purpose is an earnings-date lookup helper, not a valuation-report generator. This is materially different in scope, outputs, and capabilities.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
There is a clear description-behavior mismatch. The declared purpose describes an end-to-end valuation/reporting skill with data collection, external research, and polished report generation. The actual code chunk is limited to ownership-related data retrieval from yfinance for a ticker and prints structured JSON. While insider/institutional ownership data could be one small supporting input to a company analysis workflow, this code by itself does not match the declared primary purpose, output format, or breadth of functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description centers on a multi-step equity research and valuation-report pipeline, including quantitative data collection, web research, and polished HTML/PDF report creation. The supplied code does none of that. Its primary purpose is narrow and different: retrieving options-market data for the nearest expiry and summarizing options sentiment/activity metrics. That is materially closer to derivatives screening or trading-flow analysis than comprehensive company valuation reporting. While options data could theoretically be a small supporting input in a broader research pipeline, this code chunk exposes a distinct undeclared capability and does not implement the described report-generation behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
There is a clear description-behavior mismatch. The declared purpose emphasizes fundamental/company valuation reporting, qualitative research, analyst views, catalysts/risks, and polished report generation. The actual code only retrieves historical price data and calculates technical indicators commonly used for chart/trading analysis. That is not a supporting implementation detail of the declared report-generation pipeline as presented; it is a distinct analytical capability, especially since the description explicitly says the skill is not for quick price checks or real-time trading signals. While technicals could theoretically be one small component of broader analysis, this code chunk by itself does not implement the described valuation-report workflow and instead provides a materially different primary function.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This is a clear mismatch. The description claims a multi-step stock valuation and company analysis workflow, including quantitative data collection, web research, and polished report generation. The actual code does none of that. It only processes tweet-like JSON objects from stdin, applies engagement-based filtering thresholds, sorts by engagement, and emits JSON. While social-media research could theoretically be a tiny supporting component of a broader analysis system, this code chunk is narrowly focused on tweet filtering and does not itself implement or directly support the declared primary purpose in a recognizable way. Its primary purpose is materially different from the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The code chunk’s behavior is much narrower than the declared description. It only creates chart images from yfinance data and returns their paths in JSON. While charts could be a supporting component of a valuation report workflow, this chunk does not itself produce polished HTML/PDF reports, perform broader data collection in parallel, or do qualitative web research. Because the declared purpose presents a comprehensive reporting pipeline and this code only implements a limited chart-generation utility, the description does not accurately represent what this specific code chunk actually does.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to use shell, file reads, and file writes, but the manifest does not declare any explicit tool scope or allowed-tools boundary. That weakens least-privilege controls and makes it easier for the skill to invoke more capability than a reviewer or runtime policy expects.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The invocation text uses broad finance triggers like stock valuation, company analysis, investment thesis, and deep-dive on a ticker, which can cause the skill to activate for a wide range of normal user requests. Over-broad routing increases the chance of unnecessary shell execution, web access, and local file writes in contexts where the user did not explicitly request such actions.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill directs the agent to write multiple artifacts to /tmp, including research JSON, HTML, and PDF, without warning the user that local files will be created. This can expose sensitive prompts, research content, or generated reports to other local processes/users depending on system configuration, and it reduces transparency about persistence side effects.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manifest describes generating comprehensive valuation reports, but this file goes further by algorithmically assigning verdicts such as 'Strong Buy', 'Buy', 'Hold', and 'Sell' and later presenting a 12-month base case target and upside framing. That recommendation engine is a stronger advisory capability than simply assembling a valuation report and is not explicitly declared in the manifest.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The manifest frames this skill as producing deep-dive valuation reports and explicitly says it is not for quick price checks, daily watchlist alerts, or real-time trading signals. This code adds a dedicated Technical Analysis section with RSI, SMA, MACD, overbought/oversold labels, and a synthesized 'Technical Signal' callout, which is a trading-signal style capability not justified by the stated non-trading purpose.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Run a script and return parsed JSON or error dict."""
    cmd = [sys.executable, os.path.join(SCRIPT_DIR, name)] + args
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        if result.returncode != 0:
            return {"error": result.stderr.strip() or f"{name} exited with code {result.returncode}"}
        return json.loads(result.stdout)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
79% confidence
Finding
This Python file makes external data fetches through yfinance when constructing a Ticker object and accessing `t.info`, which can transmit the requested tickers and system network metadata to a third-party service. The script has no confirmation prompt or user-facing notice in runtime output describing that a network call will be made.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This Python file makes external data requests via yfinance when constructing a Ticker object and accessing remote-backed properties such as insider transactions. While the module docstring says it fetches data, there is no explicit runtime notice, comment, or warning that using the script will contact an external service and transmit the requested ticker symbol.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This Python file makes external network requests through yfinance when creating the ticker object and fetching options/option-chain data. While the module docstring says it fetches options data, there is no explicit user-facing notice in code output, confirmation, or warning that the script will contact a third-party service and transmit the requested ticker.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The report template sets `<html lang="en">`, which forces an English locale marker regardless of user preference or input context. Under the policy, locale or language constraints should be optional or clearly justified rather than imposed silently.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This code writes a generated HTML report to a filesystem path derived from input data or a user-supplied --output argument, but the only disclosure is a post-write success message. There is no pre-write warning, confirmation, or explanatory comment/docstring near the write operation indicating that a file will be created or overwritten.

Static analysis

No suspicious patterns detected.