Back to skill

Security audit

BudgetRcd

Security checks for vulnerabilities and agentic risk

Overview

This budget skill has a coherent purpose, but its code can write or copy files outside the intended budget area and does not reliably enforce confirmation for sensitive changes.

Review this skill carefully before installing. It is not designed to exfiltrate data or run remote code, but it handles personal finance records and local receipt files with weak path validation and weak confirmation enforcement. Use it only in a trusted local environment, avoid passing arbitrary file paths as receipt images, and prefer a fixed version that validates dates and filenames, enforces confirmation for edits/deletes, and actually honors the configured root directory.

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

T09 · Insecure Skill Coding Practices

Error
Location
src/config_manager.py:32
Finding

Path Traversal Through Unvalidated Month Values

Content
View full analysis

Vulnerability Details

File Location: src/config_manager.py:32-53; equivalent path construction also occurs in src/expense_manager.py:27-35
Vulnerability Type: Path traversal and arbitrary JSON file access
Risk Level: High

Vulnerable Code

python
def get_budget(month=None):
    """
    Read monthly budget data.
    month: YYYY-MM format; the current month is used when omitted.
    """
    if month is None:
        import datetime
        month = datetime.datetime.now().strftime("%Y-%m")
    m = month.replace("-", "")
    path = DATA_DIR_DATA / f"budget_{m}.json"
    data = _read_json(path)
    return data if data else None

def set_budget(month, total, pools=None, daily_budget=100, weekly_budget=750, note=""):
    """
    Set a monthly budget.
    """
    m = month.replace("-", "")
    path = DATA_DIR_DATA / f"budget_{m}.json"
    data = {
        "month": month,
        "total": total,
        "pools": pools or {},
        "daily_budget": daily_budget,
        "weekly_budget": weekly_budget,
        "note": note
    }
    _write_json(path, data)
    return data

The corresponding expense path construction is:

python
def _get_file_path(month=None):
    """Get the expense file path."""
    if month is None:
        month = datetime.now().strftime("%Y-%m")
    m = month.replace("-", "")
    return DATA_DIR_DATA / f"expense_{m}.json"

Technical Analysis

The month parameter is treated as though it always follows the documented YYYY-MM format, but no validation enforces that requirement. Calling replace("-", "") only removes hyphens; it does not remove path separators or neutralize .. traversal components.

The resulting attacker-controlled string is interpolated into a filename and joined to DATA_DIR_DATA. The path is neither resolved nor checked for containment under the intended data directory. For writes, _write_json() ...[truncated 1458 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate every month before path construction with a strict full-string rule such as ^\d{4}-(0[1-9]|1[0-2])$.

  • Parse the value using datetime.strptime(month, "%Y-%m") and regenerate the canonical filename from the parsed date.

  • Resolve the final path and verify containment before every read or write:

    python
    base = DATA_DIR_DATA.resolve()
    candidate = (base / f"budget_{canonical_month}.json").resolve()
    if candidate.parent != base:
        raise ValueError("Invalid budget path")
    
  • Apply the same centralized validation to budget and expense paths.

  • Reject path separators, null bytes, traversal components, and noncanonical date values.

  • Add tests covering ../, nested separators, absolute-path syntax, malformed months, and valid boundary months.

T09 · Insecure Skill Coding Practices

Error
Location
src/expense_manager.py:150
Finding

Arbitrary Local File Copy and Destination Path Manipulation in Receipt Handling

Content
View full analysis

Vulnerability Details

File Location: src/expense_manager.py:150-166
Vulnerability Type: Unrestricted local file access and path traversal
Risk Level: High

Vulnerable Code

python
def copy_image_to_budget(source_path, date, merchant_keyword):
    """
    Copy an image into the budget-data/images directory.
    Return the new image path.
    """
    if not source_path or not Path(source_path).exists():
        return None
    
    IMAGES_DIR.mkdir(parents=True, exist_ok=True)
    new_name = f"{date}_{merchant_keyword}.jpg"
    new_path = IMAGES_DIR / new_name
    
    import shutil
    shutil.copy2(source_path, new_path)
    return str(new_path)

The entry point forwards caller-controlled values directly to this function:

python
def add_expense_record(date, pool, amount, note, image_path=None, source_text=None):
    new_path = None
    if image_path:
        new_path = copy_image_to_budget(image_path, date, note[:10])
    
    return add_expense(
        date=date,
        pool=pool,
        amount=amount,
        note=note,
        image_path=new_path,
        source_text=source_text
    )

Technical Analysis

source_path is accepted as any existing filesystem path. The implementation does not verify that it came from a trusted upload mechanism, is located in an approved source directory, or contains a valid image. Consequently, any file readable by the process can be copied and relabeled with a .jpg extension.

The destination filename is constructed from unsanitized date and merchant_keyword values. Both may contain path separators or traversal components. In particular, pathlib discards the preceding base directory when the right-hand operand is an absolute path. A date beginning with an absolute path can therefore cause IMAGES_DIR / new_name to resolve outside IMAGES_DIR. Relative separators and traversal components can also m ...[truncated 1586 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not accept unrestricted filesystem paths from Agent or user input. Use trusted upload objects or allowlisted staging directories.
  • Resolve the source path and verify that it is contained in an explicitly approved upload directory.
  • Validate the file using content-based image decoding rather than filename extensions alone.
  • Impose file-size, pixel-dimension, and supported-format limits.
  • Generate destination names entirely on the application side, for example with a UUID. Do not place date, note text, or merchant text directly into paths.
  • Resolve the destination and verify that it remains a direct child of IMAGES_DIR.
  • Use exclusive creation or an explicit collision policy to prevent silent overwrites.
  • If user-visible labels are required, store them as JSON metadata rather than as path components.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
agent.py:84
Finding

User Confirmation Requirement Is Not Enforced for Destructive Expense Operations

Content
View full analysis

Vulnerability Details

File Location: agent.py:84-119
Vulnerability Type: Missing authorization enforcement for modification and deletion
Risk Level: Medium

Vulnerable Code

python
def update_expense_record(date, pool, amount, note, new_amount=None, new_note=None, new_image_path=None):
    """
    Update an existing expense record [confirmation required].
    """
    from src.expense_manager import update_expense
    if new_amount is None: new_amount = amount
    if new_note is None: new_note = note
    
    new_path = None
    if new_image_path:
        new_path = copy_image_to_budget(new_image_path, date, new_note[:10])
    
    return update_expense(date, pool, new_amount, new_note, new_path)

def delete_expense_record(date, pool, amount, note):
    """
    Delete an expense record [confirmation required].
    """
    from src.expense_manager import delete_expense
    return delete_expense(date, pool, amount, note)

def need_confirmation(action, record_desc):
    """
    Check whether an operation requires confirmation.
    """
    if action in ("update", "delete", "modify", "remove"):
        return f"Please confirm the {action} operation for this record: {record_desc}"
    return None

The lower-level deletion function also executes immediately:

python
def delete_expense(date, pool, amount, note):
    month = date[:7]
    path = _get_file_path(month)
    records = _read_expenses(path)
    
    for i, r in enumerate(records):
        if r["date"] == date and r["pool"] == pool and r["amount"] == amount and r.get("note") == note:
            deleted = records.pop(i)
            _write_expenses(path, records)
            return deleted
    
    return None

Technical Analysis

The source comments and docstrings state that update and deletion require user confirmation, but this policy is not enforced by the mutation functions. ...[truncated 1560 chars]

Remediation
View remediation

Remediation Suggestions

  • Enforce confirmation inside the mutation layer rather than relying on comments or Agent behavior.
  • Use a short-lived, single-use confirmation token bound to:
    • the operation type;
    • the exact record identifier;
    • the proposed new values;
    • the current user or session;
    • an expiration time.
  • Require and validate that token in every update and deletion function.
  • Expose destructive operations only through one guarded interface; make lower-level mutation helpers private or require the same authorization object.
  • Assign immutable record IDs when expenses are created so confirmation can target one exact record.
  • Record an audit log of approval, mutation time, record ID, and before/after values.
  • Add tests proving that direct calls, expired approvals, approvals for other records, and reused tokens are rejected.

T09 · Insecure Skill Coding Practices

Note
Location
src/config_manager.py:8
Finding

Documented Filesystem Configuration Is Ignored in Favor of Hardcoded Paths

Content
View full analysis

Vulnerability Details

File Location: src/config_manager.py:8-10; the same hardcoded base appears in src/expense_manager.py:12-14
Vulnerability Type: Security configuration bypass and incorrect filesystem boundary enforcement
Risk Level: Low

Vulnerable Code

python
import os, json
from pathlib import Path

DATA_DIR = Path("~/Documents/02_Personal/01_Budget").expanduser()
CONFIG_DIR = DATA_DIR / "config"
DATA_DIR_DATA = DATA_DIR / "data"

The expense module independently repeats the hardcoded location:

python
DATA_DIR = Path("~/Documents/02_Personal/01_Budget").expanduser()
DATA_DIR_DATA = DATA_DIR / "data"
IMAGES_DIR = DATA_DIR / "images"

This conflicts with the mandatory behavior declared in SKILL.md:17-25:

text
Every time the skill executes, it must first read the personal configuration:
~/.openclaw/workspace/skills/budgetRcd/budgetRcd.config.json

All file operations may only occur under paths.root from the personal configuration.
Hardcoded paths or any other locations are prohibited.

Technical Analysis

The packaged budgetRcd.config.json defines paths.root, paths.data, paths.config, and paths.images, but the executable modules never load these values. Instead, they independently hardcode a location in the user's home directory.

As a result, changing the documented configuration does not change the actual filesystem target. The security boundary represented by paths.root is therefore not enforced. This discrepancy can cause operations to read from or write to an unintended preexisting directory, even when the user has selected a more restricted or isolated location.

Attack Path

  1. A user or administrator configures paths.root and its child paths to point to an isolated budget workspace.
  2. The Skill is invoked under the assumption that all file operations will remain in that configured workspace.
  3. The Python modules ignore ...[truncated 695 chars]
Remediation
View remediation

Remediation Suggestions

  • Implement one shared configuration loader that reads the documented budgetRcd.config.json before any operation.
  • Canonicalize paths.root and derive the data, configuration, and image directories from that trusted root.
  • If separate child paths remain configurable, resolve each one and verify that it is contained under the canonical root.
  • Remove duplicated hardcoded path constants from all modules.
  • Fail closed when the configuration is absent, malformed, unexpectedly writable by untrusted users, or defines paths outside the permitted root.
  • Consider validating configuration-file ownership and permissions where the runtime environment supports it.
  • Add integration tests proving that changing paths.root changes every read and write target and that no operation reaches the old hardcoded location.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (12)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
78% confidence
Finding

Although the header includes bilingual text, the operational instructions and all assistant response examples are written as mandatory Chinese outputs such as "记上了!" and "看到照片了!这是什么?花了多少?" without indicating that the user may choose another language. This can violate language/locale policy when a skill effectively defaults to a specific language without explicit user opt-in.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger list contains very generic terms such as 'budget', 'spent', 'remaining', '本月', and '花了多少', which can match ordinary conversation unrelated to bookkeeping. This can cause unintended activation of the skill, leading it to read personal configuration and budget files or modify expense records when the user did not explicitly intend to invoke it.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The add operations perform persistent state changes and may also copy a local file into the budget store without any built-in confirmation, warning, or authorization check. In an agent setting, this creates a meaningful safety risk because a prompt-influenced or mistaken invocation can write fraudulent records or exfiltrate/retain unintended local files through the image-copy path.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This JSON config contains natural-language content and locale settings that are fixed to Chinese/China-specific conventions, including a Chinese comment, Chinese category names, Chinese pool-name mappings, and the Asia/Shanghai timezone. Under the policy rule, forcing a specific language or locale without an explicit user choice or documented justification is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code file contains natural-language instructions and descriptions in Chinese only, including the top-level module description and function comments. The policy requires avoiding forced language or locale constraints unless the skill offers user opt-in or clearly documents that the tool is region-specific, which is not present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The module description and docstrings are written entirely in Chinese and the default configuration hard-codes a Shanghai timezone, with no indication that language or locale is optional or user-selectable. This can violate language/locale policy when a skill implicitly assumes a specific locale rather than offering choice or documenting a justified region-specific scope.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This function extends and rewrites expense JSON files, which is a safety-relevant file modification operation. Unlike the update/delete functions, the docstring does not indicate any confirmation requirement, and there is no print/log/prompt disclosing that persistent data will be modified in bulk.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The docstring at L102-L104 says this function updates an existing record, which implies modifying a prior record's values. However, the lookup condition at L109-L110 requires date, pool, amount, and note to already equal the provided values, and then rewrites the same values back at L111-L118, so it cannot meaningfully change those fields.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The function copies a user-supplied file into a persistent images directory, which is a file-write side effect. There is no confirmation prompt, logging/print statement, or warning in the docstring that a source image will be duplicated onto disk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

Natural-language comments, docstrings, and confirmation text in this file are presented in Chinese, including user-facing confirmation wording, without indicating that the user can choose a language. This can violate language/locale policy when a skill effectively forces a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The helper performs file writes to the user's Documents budget directory, but there is no confirmation prompt, logging, or user-facing disclosure at the write site. Because this code modifies user data on disk, the operation should be clearly disclosed somewhere in code comments, runtime output, or accompanying skill documentation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The natural-language interface description and function documentation are presented only in Chinese, which can impose a language constraint on users without opt-in. The file does not state that the skill is region-specific or provide an alternative language option.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.