Back to skill

Security audit

Chart Data Viz

Security checks for vulnerabilities and agentic risk

Overview

This skill is a small local charting helper that writes chart images and history locally, with no evidence of deception, network exfiltration, or privilege-seeking behavior.

Install only if you are comfortable with chart titles, labels, numeric values, and output paths being saved locally under ~/.openclaw/workspace/memory/chart. Avoid using it for highly sensitive datasets unless local chart history storage is acceptable, and consider hardening the temporary-file save logic before shared or multi-user deployments.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lib/storage.py:23
Finding

Predictable Temporary File Permits Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/lib/storage.py:23-27
Vulnerability Type: Unsafe temporary-file handling
Risk Level: Medium

Vulnerable Code

python
def _atomic_save(path, data):
    ensure_dir()
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    os.replace(tmp, path)

Technical Analysis

The _atomic_save function uses a fixed temporary-file name derived by appending .tmp to the destination path. It opens that path with the normal "w" mode, which neither creates the file exclusively nor prevents symbolic-link traversal.

If another local process can write to ~/.openclaw/workspace/memory/chart/, it can create charts.json.tmp as a symbolic link to a file writable by the account running the skill. When the skill saves chart metadata, open() follows the symbolic link and truncates the target before writing JSON data. The subsequent os.replace() operation does not undo the modification already made through the link.

Attack Path

  1. An attacker obtains write access to the chart storage directory under the victim account.
  2. The attacker creates charts.json.tmp as a symbolic link to another file that the victim account can write.
  3. The attacker waits for or induces execution of make_chart.py, which eventually calls save_charts().
  4. _atomic_save() opens the predictable temporary path in write mode.
  5. The operating system follows the symbolic link, truncating the linked target and replacing its contents with chart metadata.
  6. The target file is corrupted or overwritten under the privileges of the account running the skill.

Impact Assessment

Successful exploitation permits corruption or overwrite of an arbitrary file writable by the skill's operating-system account. This could cause loss of user data or alter user-level configuration, depending on the selected ta ...[truncated 261 chars]

Remediation
View remediation

Remediation Suggestions

  • Create the temporary file securely and unpredictably in the destination directory with tempfile.NamedTemporaryFile(delete=False, dir=destination_directory, mode="w", encoding="utf-8").
  • Ensure the temporary file is created exclusively and is not a symbolic link.
  • Flush buffered data and call os.fsync() before atomically replacing the destination when durability is required.
  • Place os.replace() in a guarded workflow and remove the temporary file on failure.
  • Enforce owner-only permissions on the storage directory and metadata files, such as 0700 for directories and 0600 for files.
  • Where supported, use secure descriptor-based operations or no-follow semantics to further reduce symbolic-link race risks.

A hardened implementation should resemble:

python
import os
import tempfile

def _atomic_save(path, data):
    ensure_dir()
    directory = os.path.dirname(path)
    tmp_path = None

    try:
        with tempfile.NamedTemporaryFile(
            mode="w",
            encoding="utf-8",
            dir=directory,
            prefix=".charts-",
            suffix=".tmp",
            delete=False,
        ) as f:
            tmp_path = f.name
            json.dump(data, f, indent=2, ensure_ascii=False)
            f.flush()
            os.fsync(f.fileno())

        os.replace(tmp_path, path)
        tmp_path = None
    finally:
        if tmp_path is not None:
            try:
                os.unlink(tmp_path)
            except FileNotFoundError:
                pass
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description presents a full chart generation engine with data visualization capabilities. The supplied code chunk is limited to storage initialization: it creates chart-related directories and writes an initial JSON metadata file if missing. While storing outputs locally is consistent with part of the description, the primary behavior of this code is setup/initialization rather than chart creation or analysis. This is a material mismatch in actual behavior versus declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description centers on generating charts locally from data and helping users choose appropriate visualizations. The supplied code chunk instead reads stored chart records via load_charts() and prints existing chart metadata and output locations. This is materially different from the declared primary purpose for this chunk: it is an inventory/listing utility for previously created charts, not a chart generation engine. While listing charts could be a supporting function within a broader chart system, taken on its own this code does not match the declared behavior and introduces a distinct capability of enumerating stored chart outputs and paths.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description centers on chart generation and local storage of produced visualizations, with broad support for visualizing datasets and choosing suitable chart types. The code provided does not generate any charts at all; it only prints a suggestion for either a bar or line chart using basic rules based on label count and an x-axis flag. It also does not read CSV/JSON files as files, does not create visual explanations, and does not store outputs. While chart-type recommendation is one small part of the declared purpose, the primary described capability is materially overstated relative to the actual implementation.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
81% confidence
Finding

The skill advertises local storage and output generation, which implies file read/write behavior, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity: an agent may invoke filesystem-capable code without a clearly bounded contract, increasing the risk of unintended access to workspace data or future scope creep.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill description uses very broad activation triggers like 'whenever the user wants to visualize data' or 'decide which chart type fits a dataset best,' which can cause over-invocation on common data-analysis requests. In an agent system, overly broad routing increases the chance the skill accesses or stores user data unnecessarily, especially since it writes artifacts into workspace memory.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The _atomic_save function writes JSON data to files under ~/.openclaw/workspace/memory/chart and replaces the destination atomically, but there is no confirmation prompt, logging, print statement, or explanatory comment/docstring in this file describing that persistent user data will be created or updated. For a code file, this qualifies as a file-write operation lacking visible disclosure.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.