Back to skill

Security audit

Market Chart Renderer

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real chart renderer, but it needs review because it can write files outside its documented output folder and its generated charts execute mutable remote JavaScript.

Review before installing. Use it only in a workspace where generated files are acceptable, do not pass untrusted --output-stem values, and prefer fixing path containment, safe HTML data embedding, and a pinned or local ECharts dependency before relying on its output.

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)

T09 · Insecure Skill Coding Practices

Error
Location
render_chart.py:316
Finding
Arbitrary File Write Through Unsanitized Output Stem<![CDATA[ ## Vulnerability Details **File Location**: `render_chart.py`, lines 316–320 and 354 **Vulnerability Type**: Path traversal and unrestricted output path **Risk Level**: High ### Vulnerable Code ```python stem = output_stem or f"{symbol.lower()}_{period}m_market_chart_renderer" base = OUTDIR / stem json_path = base.with_suffix('.json') html_path = base.with_suffix('.html') png_path = base.with_suffix('.png') ``` The value is supplied directly through a command-line argument: ```python parser.add_argument('--output-stem', default=None) ``` The resulting paths are subsequently written without containment validation: ```python html_path.write_text(build_html(payload), encoding='utf-8') json_path.write_text( json.dumps( {'chart_input': chart_input, 'payload': payload}, ensure_ascii=False, indent=2, ), encoding='utf-8', ) ``` ### Technical Analysis The user-controlled `output_stem` is combined with `OUTDIR` without rejecting absolute paths, parent-directory components, or path separators. Python's `pathlib` discards the left operand when the right operand is absolute. Therefore, an absolute `output_stem` causes `base` to resolve outside `OUTDIR`. A relative value containing `../` can also escape the intended output directory. Calling `with_suffix()` changes the final suffix but does not prevent traversal or restore containment. The application then writes HTML and JSON directly to the resulting paths. When PNG generation is enabled, the path is also passed to Chrome through `--screenshot`. ### Attack Path 1. An attacker obtains control over the command-line arguments used to launch the renderer. 2. The attacker supplies an absolute or traversing output stem, for example: ```bash python3 render_chart.py \ --symbol MA0 \ --period 60 \ --no-png \ --output-stem ../../attacker-selected/location ``` 3. `OUTDIR / output_stem` resolves outside the documented image output directory. 4. `wi ...[truncated 872 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict `output_stem` to a simple filename stem rather than accepting a path. - Reject absolute paths, `..`, path separators, NUL characters, and unexpected filename characters. - Resolve both the output directory and candidate destination, then verify containment before writing: ```python import re def safe_output_base(output_stem: str) -> Path: if not re.fullmatch(r'[A-Za-z0-9._-]+', output_stem): raise ValueError('output stem contains unsupported characters') root = OUTDIR.resolve() candidate = (root / output_stem).resolve() if candidate.parent != root: raise ValueError('output path escapes the output directory') return candidate ``` - Apply the containment check independently to every final path before writing. - Consider exclusive file creation or an explicit overwrite option if existing files must not be replaced silently. - Run the renderer under a dedicated, least-privileged account with write access limited to the intended output directory. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
render_chart.py:273
Finding
JavaScript Injection Through Unsafe JSON Embedding in Generated HTML<![CDATA[ ## Vulnerability Details **File Location**: `render_chart.py`, lines 273–298 **Vulnerability Type**: Script-context injection in generated HTML **Risk Level**: High ### Vulnerable Code ```python def build_html(payload: dict[str, Any]) -> str: option = build_option(payload) option_json = json.dumps(option, ensure_ascii=False) payload_json = json.dumps(payload, ensure_ascii=False) return f'''<!doctype html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Market Chart Renderer</title> <style> html, body {{ margin: 0; width: 1400px; height: 900px; background: #0b1220; overflow: hidden; }} #chart {{ width: 1400px; height: 900px; }} </style> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> </head> <body> <div id="chart"></div> <script> window.__PAYLOAD__ = {payload_json}; const chart = echarts.init(document.getElementById('chart'), null, {{ renderer: 'canvas' }}); const option = {option_json}; chart.setOption(option); window.__ECHARTS_DONE__ = true; </script> </body> </html>''' ``` ### Technical Analysis `payload_json` and `option_json` contain values derived from chart and instrument data. They are serialized as valid JSON and inserted directly into an inline HTML `<script>` element. JSON string escaping is not sufficient for HTML script context. In particular, `json.dumps()` does not necessarily neutralize the HTML parser sequence `</script>`. The browser recognizes that sequence as the end of the script element even when it appears inside a JavaScript string literal. Consequently, an attacker-controlled string such as the following can terminate the intended script and inject markup or JavaScript: ```text </script><script>/* attacker-controlled JavaScript */</script> ``` Potentially affected values include strings propagated from the upstream chart payload, such as instrument contract, ...[truncated 1433 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not insert ordinary JSON serialization directly into executable script blocks. - Serialize JSON using a script-context-safe encoder that replaces at least: - `<` with `\u003c` - `>` with `\u003e` - `&` with `\u0026` - U+2028 with `\u2028` - U+2029 with `\u2029` - For example: ```python def script_safe_json(value: Any) -> str: return ( json.dumps(value, ensure_ascii=False) .replace('&', '\\u0026') .replace('<', '\\u003c') .replace('>', '\\u003e') .replace('\u2028', '\\u2028') .replace('\u2029', '\\u2029') ) ``` - Prefer storing serialized data in a non-executable `<script type="application/json">` element and parsing its text content, while still escaping `<` to prevent premature element termination. - Validate upstream fields against expected schemas and lengths. Instrument identifiers and frequencies should generally use restrictive allowlists. - Add a restrictive Content Security Policy. If inline scripts are necessary, use a generated nonce rather than allowing unrestricted inline JavaScript. - Add regression tests containing `</script>`, HTML tags, quotes, U+2028, and U+2029 in every externally derived string field. ]]>

T08 · Insecure Dependencies

Warning
Location
render_chart.py:290
Finding
Execution of Mutable Third-Party JavaScript From an Unpinned CDN Dependency<![CDATA[ ## Vulnerability Details **File Location**: `render_chart.py`, line 290 **Vulnerability Type**: Unpinned remote JavaScript dependency without integrity verification **Risk Level**: Medium ### Vulnerable Code ```html <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> ``` ### Technical Analysis Every generated chart references ECharts from a public CDN at runtime. The dependency uses the floating major-version selector `@5` rather than an exact immutable version. It also lacks a Subresource Integrity hash. As a result, the JavaScript executed when the chart is opened or rendered is not fully represented by the audited project. A future package release, compromised package publication, CDN compromise, or unexpected CDN resolution behavior could cause different JavaScript to execute without any local source-code change. The same dependency is loaded during headless Chrome PNG generation, making chart rendering dependent on both network availability and the current content returned by the external service. ### Attack Path 1. A user runs the renderer, which generates an HTML file containing the remote script reference. 2. The HTML is opened interactively or loaded by headless Chrome during PNG generation. 3. The browser requests `echarts.min.js` from jsDelivr. 4. The CDN resolves the floating `@5` selector and returns JavaScript not cryptographically verified by the generated page. 5. If the package or delivery path has been compromised, the browser executes the substituted JavaScript in the chart document. 6. The substituted code can manipulate the generated visualization, initiate network traffic, and access information available to that browser document. ### Impact Assessment A compromised dependency can execute JavaScript in every generated chart at view or rendering time. This can undermine chart integrity, falsify screenshots, disclose chart payload data over the network, and make rendering behavior change afte ...[truncated 366 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer vendoring a reviewed ECharts build inside the project and loading it from a local, immutable path. - If a CDN is required, pin an exact ECharts version rather than using `@5`. - Specify an exact asset path and add a verified Subresource Integrity hash: ```html <script src="https://cdn.jsdelivr.net/npm/echarts@EXACT_VERSION/dist/echarts.min.js" integrity="sha384-VERIFIED_HASH" crossorigin="anonymous"></script> ``` - Record the exact dependency version and hash in project dependency metadata. - Restrict permitted script origins with Content Security Policy. - Ensure PNG rendering can operate offline so output generation does not silently depend on mutable external content. - Include dependency update review and integrity-hash regeneration in the release process. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises capabilities that involve shell execution and writing files, but it does not declare any explicit tool scope or permission boundaries. That omission increases the chance that an agent invokes the skill with broader-than-necessary privileges, leading to unintended command execution or filesystem modification if downstream scripts are misused.

Natural-Language Policy Violations

Medium
Confidence
82% confidence
Finding
SQP-3 covers natural-language policy violations such as forcing a specific language or locale without user opt-in. The summary explicitly frames the skill as `中国市场` and the document is entirely in Chinese, with no indication that users may choose another language or that the locale restriction is optional.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
A skill named market-chart-renderer is justified in transforming provided market data into visual outputs, but this file also reaches into another skill module and calls build_chart_payload to obtain the underlying chart data. That cross-skill data acquisition capability is not an obvious requirement of a renderer if its stated purpose is only rendering charts.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The runtime warning text is hard-coded in Chinese, and other user-visible strings in the generated chart are also fixed in Chinese, with no option to select language or locale. This is a natural-language policy issue because the skill imposes a specific language on all users without opt-in or documented locale scope.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The chart title text includes fixed Chinese labels such as '最新', and the series name at L232 uses 'K线', making the rendered output language-specific. Because the file offers no localization setting or opt-in, it forces a specific language in user-visible output.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The generated HTML loads ECharts from a remote CDN at render time, which introduces undeclared network access and a supply-chain trust dependency into what otherwise appears to be a local renderer. Because the HTML is then opened in headless Chrome for screenshot generation, a compromised CDN response or unexpected network behavior could influence execution inside the browser context.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Loading a remote JavaScript library without notifying the user that rendering triggers network access is a real security-relevant transparency issue, especially in tooling expected to operate locally. In a security-sensitive or offline environment, undisclosed outbound requests can leak usage metadata and create an avoidable external execution dependency.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
f'--screenshot={png_path}',
        html_path.resolve().as_uri(),
    ]
    subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return chrome_binary
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
89% confidence
Finding
For markdown files, SQP-2 applies when the skill description omits warnings about behaviors that can affect user data or system state. This file documents output paths under `output/generated/images/*` but does not warn that running the skill will create or replace files there.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This code performs file writes to generated output paths, including HTML and JSON unconditionally and PNG conditionally, but the write operations themselves have no confirmation prompt, warning print, or inline comment/docstring explaining that files will be created. For code files, file writes are in scope when they lack any form of user disclosure and are not explicitly surfaced at the point of action.

Static analysis

No suspicious patterns detected.