Back to skill

Security audit

spending-log

Security checks for vulnerabilities and agentic risk

Overview

This is mostly a normal expense logging skill, but it can set up recurring monthly report delivery and back up private financial reports to a fixed folder without a clear opt-in step.

Review this before installing if your spending data is sensitive. Require explicit confirmation before creating, editing, deleting, exporting, backing up, or scheduling reports; disable or inspect any openclaw cron task; and change the backup location if /volume2/Download/OpenClaw/reports/ is shared or synchronized.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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

Warning
Location
scripts/report.py:162
Finding

Stored HTML Attribute Injection in Generated Reports

Content
View full analysis

Vulnerability Details

File Location: scripts/report.py:162-164
Related Validation Gap: scripts/module.py:38-43
Vulnerability Type: Stored HTML injection
Risk Level: Medium

Vulnerable Code

python
pie_svg_parts.append(
    f'<path class="pie-seg" d="{d}" fill="{cfg["color"]}" '
    f'data-cat="{cat}" data-amount="{amount:.2f}" data-pct="{pct:.1f}" '
    f'style="--tx:{tx}px;--ty:{ty}px"/>'
)

The data-loading validation only confirms that required keys exist:

python
for i, rec in enumerate(data):
    if not isinstance(rec, dict):
        raise ValueError(f"Record {i+1} is not an object: {rec!r}")
    for field in REQUIRED_FIELDS:
        if field not in rec:
            raise ValueError(
                f"Record {i+1} is missing field '{field}': {rec!r}"
            )

Technical Analysis

The category value is inserted directly into the quoted data-cat HTML attribute. It is not escaped with html.escape(value, quote=True) at this output location.

Although records normally created by crud.py restrict categories to the configured allowlist, report.py accepts an arbitrary expenses file through --file. The shared load_data() function only checks the container type and presence of required fields. It does not enforce the category allowlist, validate field types, or reject attribute-breaking characters.

Consequently, a crafted JSON record can close the data-cat attribute and introduce an event-handler attribute. Because the generated report includes executable JavaScript and is intended to be opened in a browser, this becomes stored client-side script execution.

Attack Path

  1. An attacker creates or modifies an expenses JSON file containing all required keys.
  2. The attacker assigns the category field a value such as:
    text
    " onmouseover="alert(document.domain)
    
  3. The victim or Agent invokes:
    bash

...[truncated 940 chars]

Remediation
View remediation

Remediation Suggestions

  1. Escape every dynamic value used in HTML attributes:
    python
    safe_cat = html_mod.escape(str(cat), quote=True)
    
    Use safe_cat in data-cat and all other attribute contexts.
  2. Enforce a strict schema in load_data():
    • Require category to be a member of CATEGORIES.
    • Require description, date, and id to be strings.
    • Validate dates using the expected format.
    • Require amounts to be finite, positive numeric values.
    • Require timestamps to be valid integers.
  3. Keep output encoding context-specific. Use HTML text escaping for text nodes and quote-aware escaping for attribute values.
  4. Add regression tests using quotes, angle brackets, event-handler fragments, and malformed record types.
  5. Consider applying a restrictive Content Security Policy to generated reports, ideally moving inline JavaScript to a separate trusted file or using a nonce/hash-based policy.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/report.py:1232
Finding

Spreadsheet Formula Injection in CSV Exports

Content
View full analysis

Vulnerability Details

File Location: scripts/report.py:1232-1237
Vulnerability Type: CSV formula injection
Risk Level: Medium

Vulnerable Code

python
with open(output_file, 'w', newline='', encoding='utf-8-sig') as f:
    writer = csv.writer(f)
    writer.writerow(['Date', 'Category', 'Description', 'Amount'])
    for e in month_expenses:
        writer.writerow([
            e['date'],
            e['category'],
            e.get('description', ''),
            f"{e['amount']:.2f}"
        ])

Technical Analysis

csv.writer correctly handles CSV quoting and delimiters, but CSV quoting does not prevent spreadsheet applications from interpreting cell contents as formulas.

The expense description is user-controlled and is written without neutralizing values beginning with formula-triggering characters such as =, +, -, or @. Some spreadsheet clients also ignore leading tabs or carriage returns when deciding whether to evaluate a cell.

Therefore, an expense description can become an executable spreadsheet formula when the exported file is opened in a compatible desktop spreadsheet application.

Attack Path

  1. An attacker causes an expense record to be created with a description beginning with a spreadsheet formula.
  2. For example, the description may contain a formula that references an attacker-controlled external resource.
  3. The victim generates a CSV report:
    bash
    python3 scripts/report.py --csv --month 2026-04
    
  4. The raw description is written into the CSV cell.
  5. The victim opens the exported file in a spreadsheet application that evaluates formulas.
  6. The spreadsheet evaluates the attacker's content, subject to the application's security settings and prompts.

Impact Assessment

Depending on the spreadsheet client, security configuration, and user interaction, exploitation may cause outbound requests, leak spreadsheet data, cr ...[truncated 291 chars]

Remediation
View remediation

Remediation Suggestions

  1. Sanitize every string cell before passing it to csv.writer.
  2. Detect formula-leading characters after removing or accounting for leading whitespace, tabs, carriage returns, and line feeds.
  3. Prefix potentially dangerous cells with an apostrophe or another spreadsheet-safe neutralization character:
    python
    def safe_csv_cell(value):
        value = str(value)
        checked = value.lstrip(' \t\r\n')
        if checked.startswith(('=', '+', '-', '@')):
            return "'" + value
        return value
    
  4. Apply this function to descriptions, categories, dates, labels, and any future user-controlled string fields.
  5. Document that CSV exports are intended as data rather than trusted spreadsheet programs.
  6. Add tests covering formula prefixes, leading whitespace, tabs, embedded line breaks, Unicode text, and ordinary negative numeric fields.

T05 · Unauthorized Access and Privilege Escalation

Note
Location
SKILL.md:112
Finding

Automatic Scheduled Execution and External Storage of Financial Reports Without Explicit Opt-In

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:112,130
Vulnerability Type: Excessive filesystem access and persistent scheduled execution
Risk Level: Low

Relevant Skill Instructions

The instruction at line 112 requires generated reports to be sent to the user and additionally backed up to the following fixed external directory:

text
/volume2/Download/OpenClaw/reports/

The instruction at line 130 directs the Agent to configure an OpenClaw cron task after expense tracking is first established:

text
Configure an OpenClaw cron task to generate and send the previous month's
report at 08:00 on the first day of every month. Trigger this configuration
after expense tracking is first set up.

Technical Analysis

Financial reports contain sensitive personal information. The Skill directs the Agent to copy these reports outside the Skill's private data directory into a fixed download location, which may be shared, synchronized, broadly readable, or retained under different access controls.

The Skill also directs the Agent to create a recurring scheduled task automatically after initial setup. A cron task survives the original interaction and causes future execution and report transmission. The instructions do not require a separate, explicit user confirmation before either side effect.

The Python scripts reviewed do not themselves perform the external backup or install the cron task. The risk arises if an Agent follows the instructions in SKILL.md.

Attack Path

  1. A user performs initial expense-tracking setup.
  2. The Agent follows the Skill instruction and creates a recurring OpenClaw cron task without a dedicated consent prompt.
  3. On the first day of subsequent months, the task generates and sends the previous month's report.
  4. The Agent also copies reports to the fixed external download directory.
  5. Other users, services, synchronization tools, or processes with access ...[truncated 681 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require a clear, separate user confirmation before creating any scheduled task.
  2. Show the exact schedule, command, report recipient, and data involved before obtaining consent.
  3. Do not copy reports to a fixed external location by default. Store them in a private Skill-controlled directory with restrictive permissions.
  4. If backup is requested, allow the user to select the destination and confirm whether it is shared or synchronized.
  5. Provide commands to list, disable, and delete the installed cron task.
  6. Define report retention and deletion behavior.
  7. Verify the destination and recipient before every automated transmission, or require periodic renewal of consent.
  8. Use restrictive file permissions and avoid exposing financial reports in globally readable download directories.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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 (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

代码的核心用途仍属于“个人支出记账”,这一点与声明大体相关;但声明列出的多个关键能力在该代码块中并未体现。实际代码只是面向 expenses.json 的 CRUD 数据层/CLI 工具,需要显式传入 amount/category/description/date,不具备自然语言理解;分类只能校验是否在预定义分类中,不会自动分类;也没有任何聚合统计、月度报表或预算阈值检测/提醒逻辑。因此,虽然主方向一致,但描述明显高于实际实现,存在实质性描述-行为不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

声明强调的是“个人支出记账”这一更完整的记账技能,核心能力应包括录入支出、自然语言解析、自动分类、预算提醒及报表生成。但提供的代码仅负责读取已有数据并做筛选、排序和统计输出,没有任何新增/修改账目、自然语言理解、分类推断或预算监控逻辑。它与声明中的“月度统计”部分基本一致,也能输出一定形式的月度汇总/明细,但整体主功能明显偏向‘查询统计已有支出数据’而非‘记账技能’。因此描述对代码能力有明显夸大,构成不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

声明描述的是一个完整的个人记账技能,重点包括“自然语言记账”和“自动分类”。但提供的代码没有任何用户输入解析、记账写入、消费录入或分类判定逻辑;它只调用 load_data 读取现有数据,再根据月份生成静态 HTML 报表,或导出 CSV。代码确实覆盖了声明中的一部分能力,如月度统计、预算提醒(通过预算占比/超支提示)和月度报表生成,但缺失了被明确宣称的核心能力:记账本身和自动分类。此外,代码还包含未声明的 CSV 导出以及为多个历史月份批量生成报表的行为。因此,描述不能准确代表该代码块的实际功能,存在明显不匹配。

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README advertises very broad natural-language triggers such as short everyday phrases like “午饭35” and “昨天打车花了25”, with no clear confirmation step, invocation boundary, or exclusions. In an agent context, this increases the chance of accidental activation and unintended data writes when a user is merely chatting or providing context rather than explicitly intending to invoke the skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill documentation exposes state-changing capabilities including deleting recent records, modifying entries, and writing local expense/config data, but does not warn users about these side effects or describe safeguards. In a financial logging skill, unintended edits or deletions can corrupt personal records and make users unaware that private spending data is being persisted locally.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding

The skill instructs the agent to read and write local files (data/expenses.json, data/config.json) but does not declare any explicit tool scope or permissions boundary. This weakens least-privilege controls and can allow broader-than-expected filesystem access if the runtime grants default file capabilities.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are broad enough to activate on common finance-related conversation, which can cause the skill to ingest, persist, or act on sensitive spending information when the user did not intend to invoke it. In a personal-finance context this increases privacy risk because ordinary chat may be treated as a command to store or process personal data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill states that generated HTML reports will be sent to the user and backed up to /volume2/Download/OpenClaw/reports/ without an explicit privacy notice, retention policy, or opt-in. Spending reports contain highly sensitive personal financial data, so silent export and backup increase the chance of unintended disclosure through shared storage, device compromise, or overbroad file access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Automatic monthly report generation and sending via cron creates recurring disclosure of personal finance data without an explicit, informed user consent flow. Because this is scheduled and persistent, the risk is amplified: sensitive reports may continue to be generated and transmitted long after the user forgets the setting or no longer expects the behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file’s user-facing natural language is consistently Chinese in the module docstring, CLI description, help text, and error/output messages. This imposes a specific language/locale on users without any opt-in or documented regional justification, which matches the language-policy violation criterion.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

Natural-language policy violations include forcing a specific language without user opt-in. This file presents all user-facing instructions and trigger phrases only in Chinese, with no indication that other languages are supported or that the locale constraint is intentional and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

The manifest and examples are entirely in Chinese and define Chinese trigger phrases, with no indication that the user can choose another language or locale. This can constitute a language-policy issue when a skill implicitly enforces one language without opt-in or documented justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code includes user-facing docstrings, help text, warnings, and printed output entirely in Chinese, with no option for users to select another language. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file description explicitly states the report is generated in Chinese, and the HTML later fixes the page language to Chinese as well. This enforces a specific language/locale choice without any user opt-in or explanation that the tool is region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The generated document declares a fixed zh-CN locale, which is a natural-language policy concern when no user selection mechanism is provided. Nothing in this file indicates the locale is optional or justified as a region-specific requirement.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.