Back to skill

Security audit

a-share-daily-report-publish

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly coherent as an A-share report generator, but it needs review because it turns external market data into local HTML without escaping and includes actionable stock-trading guidance.

Review before installing. Use it only in a trusted working directory with trusted market-data sources, treat the HTML report as untrusted until output escaping is fixed, and do not treat its trading sections as personalized financial advice.

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/generate.py:681
Finding

Stored HTML and Script Injection Through Unescaped Report Data

Content
View full analysis

Vulnerability Details

File Location: scripts/generate.py, lines 681-694
Vulnerability Type: Stored HTML injection / cross-site scripting
Risk Level: High

Vulnerable Code

python
def _concepts_html(concepts):
    """Format concept labels for HTML display."""
    if not concepts:
        return ''
    parts = [p.strip() for p in concepts.split('.') if p.strip()]
    if not parts:
        return ''
    html = f'<span style="color:#d29922;font-weight:600;">{parts[0]}</span>'
    for p in parts[1:]:
        html += f' <span style="color:#8b949e;font-size:11px;background:#21262d;padding:1px 6px;border-radius:3px;">{p}</span>'
    return html

The same unsafe interpolation pattern is used throughout report generation. For example, fields such as stock names, codes, reasons, tags, market summaries, source labels, and date labels are inserted directly into HTML fragments without contextual escaping.

Technical Analysis

Values loaded from report_data.json are treated as trusted HTML even though the documented workflow populates them from external market-data services. _concepts_html() places each concept string directly between HTML tags. It does not call html.escape(), sanitize markup, or validate the field against a restricted character set.

An attacker who can influence a remote data response, the intermediate JSON file, or an upstream connector can supply markup such as:

json
{
  "concepts": "MarketTheme.<img src=x onerror=\"alert(document.domain)\">"
}

This becomes active markup in the generated report:

html
<span style="color:#d29922;font-weight:600;">MarketTheme</span>
<span style="..."><img src=x onerror="alert(document.domain)"></span>

Because the output is an HTML file intended to be delivered and opened by a user, the payload executes when the report is viewed in a browser. The static template does not establish a Content Security Policy th ...[truncated 1625 chars]

Remediation
View remediation

Remediation Suggestions

  1. Escape every untrusted value at the point where it is inserted into HTML:

    python
    from html import escape
    
    def html_text(value):
        return escape(str(value), quote=True)
    

    Apply this helper to concepts, stock names, codes, reasons, labels, summaries, dates, source names, and every other string obtained from JSON or external services.

  2. Correct _concepts_html() as follows:

    python
    from html import escape
    
    def _concepts_html(concepts):
        if not concepts:
            return ''
        parts = [p.strip() for p in str(concepts).split('.') if p.strip()]
        if not parts:
            return ''
        result = (
            '<span style="color:#d29922;font-weight:600;">'
            f'{escape(parts[0])}</span>'
        )
        for part in parts[1:]:
            result += (
                ' <span style="color:#8b949e;font-size:11px;'
                'background:#21262d;padding:1px 6px;border-radius:3px;">'
                f'{escape(part)}</span>'
            )
        return result
    
  3. Prefer an auto-escaping template engine such as Jinja2 rather than assembling HTML with f-strings. Only explicitly mark constant, developer-authored fragments as safe.

  4. Validate structured fields. For example, stock codes should match an expected character pattern, dates should be parsed as dates, and numeric values should reject non-numeric input.

  5. Add a restrictive Content Security Policy, preferably via an HTTP header when hosted. For standalone reports, a suitable meta policy can provide partial defense in depth:

    html
    <meta http-equiv="Content-Security-Policy"
          content="default-src 'none'; style-src 'unsafe-inline'; img-src data:;">
    
  6. Add automated tests containing payloads such as <script>, <img onerror>, quotation marks, ampersands, and closing tags, and verify that the output contains encoded text rath ...[truncated 26 chars]

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:1363
Finding

Unvalidated Date Controls a Predictable Output File Path

Content
View full analysis

Vulnerability Details

File Location: scripts/generate.py, lines 1363-1366
Vulnerability Type: Unsafe file-path construction and symlink-following file write
Risk Level: Medium

Vulnerable Code

python
# Write output
date = data.get('date', datetime.now().strftime('%Y-%m-%d'))
out_path = f"a-share-report-{date}.html"
with open(out_path, 'w', encoding='utf-8') as f:
    f.write(html)

Technical Analysis

The date value is loaded from attacker-influenceable JSON and concatenated directly into a filesystem path. The value is not parsed as a date, checked for path separators, normalized, or constrained to remain in a designated output directory.

In addition, open(..., 'w') follows symbolic links and truncates existing files. Output names are predictable, so another local process or user with write access to the working directory can pre-create the expected output path as a symbolic link.

A traversal payload is conditional because the fixed a-share-report- prefix becomes part of the first path component. Nevertheless, slash-containing values can address nested paths where matching directories exist, and a prepared directory structure can permit .. traversal. The deterministic symlink-overwrite path is more direct: if the attacker can prepare the working directory, the script follows a symlink and overwrites its target.

The write occurs with the privileges of the user running the skill.

Attack Path

A concrete symlink attack is:

  1. The attacker determines the report date, for example 2026-09-16.

  2. In a shared or attacker-writable working directory, the attacker creates a symbolic link:

    bash
    ln -s /home/victim/.config/example.conf \
      a-share-report-2026-09-16.html
    
  3. The victim runs the generator in that directory with a JSON document whose date is 2026-09-16.

  4. Python opens the predictable path in write mode and follows the symbolic link.

...[truncated 956 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse and strictly validate the date rather than using it as an arbitrary string:

    python
    raw_date = data.get("date", datetime.now().strftime("%Y-%m-%d"))
    parsed_date = datetime.strptime(raw_date, "%Y-%m-%d")
    safe_date = parsed_date.strftime("%Y-%m-%d")
    
  2. Write only to a trusted, explicitly created output directory and verify containment:

    python
    output_dir = os.path.abspath("./reports")
    os.makedirs(output_dir, mode=0o700, exist_ok=True)
    
    out_path = os.path.abspath(
        os.path.join(output_dir, f"a-share-report-{safe_date}.html")
    )
    if os.path.commonpath([output_dir, out_path]) != output_dir:
        raise ValueError("Invalid output path")
    
  3. Refuse to overwrite existing files or symbolic links. On platforms supporting it, use os.open() with O_CREAT | O_EXCL | O_NOFOLLOW:

    python
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    if hasattr(os, "O_NOFOLLOW"):
        flags |= os.O_NOFOLLOW
    
    fd = os.open(out_path, flags, 0o600)
    with os.fdopen(fd, "w", encoding="utf-8") as output:
        output.write(html)
    
  4. Do not generate reports in shared or untrusted working directories. Ensure the output directory is owned by the executing user and is not writable by other users.

  5. If replacing an existing report is required, write to a securely created temporary file in the same trusted directory and use an atomic rename after verifying the destination is not a symlink.

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill instructs the agent to create report_data.json, run generate.py, and deliver a generated HTML file, which implies file read/write behavior, yet it declares no explicit tool scope or permission boundaries. In an agent environment, missing scope declarations can let the skill inherit broader filesystem capabilities than necessary, increasing the risk of unintended file access or overwrite if the workflow or surrounding tooling is abused.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases include broad terms like '市场复盘' and '大盘复盘', which can match ordinary market-commentary requests beyond the user’s intent to invoke this specific skill. Overly broad activation can cause the skill to run unexpectedly, leading to unnecessary external data access, file generation, or script execution in contexts where the user only wanted a general discussion.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The document declares lang="zh-CN", and the entire rendered content is written in Simplified Chinese, which imposes a specific language/locale on all users. Under the policy, this is a natural-language locale restriction unless the skill offers language choice or clearly documents a justified region-specific scope, which is not present here.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The template goes well beyond neutral market reporting and embeds concrete stock-picking, entry/avoidance guidance, and next-day trading plans. In an agent skill advertised as a daily report generator, this creates a behavioral steering risk: users may rely on AI-generated recommendations as actionable financial advice without suitability checks, jurisdictional compliance, or clear separation between data reporting and opinion.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The template contains direct investment advice and risk-management instructions such as which stocks to buy/avoid, when to add exposure, stop-loss rules, and target trading setups. This is dangerous because it can cause users to take real financial actions based on unverified automated output, increasing legal/compliance exposure and risk of user harm from unsuitable or misleading recommendations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module description and all generated natural-language report content are hard-coded in Chinese, indicating a fixed language/locale behavior. The policy requires either user choice/opt-in or a clearly documented justification for the locale restriction; this file contains neither.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest describes a generator for daily A-share review reports with market data modules, but this code produces explicit next-day trading guidance such as '重点接力', '分歧低吸', and '坚决规避'. That is a prescriptive advisory capability rather than just generating a recap/report, and it is not clearly justified by the stated purpose.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This section outputs concrete investment guidance like position sizing, stop-loss rules, and trading discipline recommendations. Those behaviors go beyond assembling a daily HTML market report from collected data and amount to investment-advice functionality not expressed in the manifest.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This code performs a file write to a new HTML report on disk, but the write operation itself has no nearby comment/docstring warning the user that a local file will be created. For code files, file writes can merit a finding when there is no direct disclosure around the action, and the main function does not explicitly warn before writing.

Content

No source excerpt is available for this finding.