Back to skill

Security audit

SMB Invoice Tracker

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed local invoice tracker, with the main cautions being optional MiniMax sharing for reminder text and local storage of invoice data.

Install only if you are comfortable storing invoice records locally under ~/.openclaw/smb-invoice-tracker. Do not use LLM reminder generation for sensitive invoices unless you are comfortable sending payer, amount, due date, and note fields to MiniMax. On shared systems, restrict permissions on the invoice tracker directory because the code relies on default filesystem permissions.

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/invoice_tracker.py:66
Finding

Invoice Ledger Created Without Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/invoice_tracker.py, lines 66–70
Vulnerability Type: Sensitive data stored with potentially excessive local read permissions
Risk Level: Medium

python
def save_invoices(invoices):
    """Save invoices to disk with schema versioning."""
    DATA_PATH.parent.mkdir(parents=True, exist_ok=True)
    wrapped = {"_schema_version": SCHEMA_VERSION, "invoices": invoices}
    with open(DATA_PATH, "w") as f:
        json.dump(wrapped, f, indent=2)

Technical Analysis

The invoice ledger contains potentially sensitive business and personal information, including payer names, invoice amounts, currencies, due dates, payment status, notes, and reminder history. The application creates its data directory and invoice file without explicitly setting restrictive permissions.

File permissions therefore depend on the process umask. Under a common 022 umask, invoices.json may be created with mode 0644, making it readable by other local users if the parent home-directory permissions allow traversal. The data directory may similarly be created with mode 0755.

This exceeds the minimum local access required by the declared functionality: only the account running the Skill needs to read or modify its invoice ledger. Although exploitation depends on host account and directory permissions, the code does not enforce this least-privilege boundary itself.

Attack Path

  1. A victim runs the add command, causing the Skill to create ~/.openclaw/smb-invoice-tracker/invoices.json.
  2. The process uses a permissive umask, such as 022, and the victim's home-directory hierarchy permits traversal by another local account.
  3. The resulting ledger is readable by users other than its owner.
  4. A local unprivileged attacker locates the predictable path and reads the file.
  5. The attacker obtains the stored payer identities, financial amounts, due dates, notes, payment status, ...[truncated 902 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create the data directory with owner-only permissions:

    python
    DATA_PATH.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    os.chmod(DATA_PATH.parent, 0o700)
    
  2. Create the ledger using an explicit 0600 mode rather than relying on the process umask. Use os.open with O_CREAT, O_WRONLY, and O_TRUNC, then wrap the descriptor with os.fdopen.

  3. Write updates atomically to an owner-only temporary file in the same directory, flush and synchronize it, and replace the destination with os.replace. This also reduces corruption risk if execution is interrupted.

  4. After replacement, explicitly enforce mode 0600 on the resulting ledger.

  5. On startup or before reading existing data, verify that both the directory and ledger are owned by the current user and are not accessible to group or other users. Reject unsafe ownership and repair permissions where appropriate.

  6. Apply the same owner-only permission controls to config.json, which is written through a similar unrestricted pattern at lines 95–99.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (8)

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 7)May include surrounding context.

md
author: "NASSER AL-SOLAITTI"
description: "Local invoice tracking ledger with optional LLM-generated reminder text. Add invoices manually, track outstanding/paid status, generate cash flow reports. Use when user says 'track an invoice', 'add an invoice', 'show my outstanding invoices', or 'invoice report'.
permissions:
  - filesystem-write: write invoice and config data to ~/.openclaw/smb-invoice-tracker/
  - env: read MINIMAX_API_KEY from environment (for optional LLM reminder generation)
  - network: send invoice fields (payer name, amount, due date, note) to minimax LLM API when reminder generation is used
privacy: |

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill transmits invoice contents including payer name, amount, due date, and note to an external LLM provider to generate reminder text. That creates an unnecessary third-party data disclosure path for potentially sensitive business information, and the feature is not clearly necessary for a local invoice-tracking utility.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

The code makes outbound network requests to a third-party API endpoint, carrying invoice-derived prompt content whenever LLM features are enabled. External transmission is not inherently malicious, but in this business context it expands the attack surface and can leak sensitive invoice information to a remote service.

Content

Scanner excerpt · scripts/invoice_tracker.py (reported line 127)May include surrounding context.

python
api_key, _ = get_llm_api_key()
    if not api_key:
        return None
    base_url = "https://api.minimax.chat/v1"  # pinned, no env override
    try:
        data = json.dumps({
            "model": "minimax/MiniMax-M3",

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The docstring says 'Send a payment reminder for a specific invoice,' and the CLI help similarly says 'Send a reminder for an invoice.' In practice, the function prints a generated message body and appends a reminder-tracking record, but it does not call email, SMS, WhatsApp, or any delivery API, so the documented intent contradicts actual behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

At the point the user invokes send-reminder, the code may forward invoice data to an external LLM service without an explicit warning or consent prompt. Because invoice fields can contain sensitive commercial and personal information, silent transmission increases privacy and compliance risk.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/invoice_tracker.py (reported line 48)May include surrounding context.

python
level = logging.INFO
    env_level = os.environ.get("CLAWHUB_LOG_LEVEL", "").upper()
    if env_level in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
        level = getattr(logging, env_level)
    logging.basicConfig(
        level=level,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring says 'Scan Gmail for new invoice-related emails,' and the CLI exposes 'scan' as if it performs that action. The body explicitly states it is a placeholder and only logs hypothetical Gmail queries, so the documentation and command intent overstate the implemented behavior.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The authorization flow can set gmail_authorized to true via --simulate without any real OAuth validation, creating a false trust state for access-control-sensitive functionality. While current scan behavior is only a placeholder, this pattern is dangerous because future code may rely on this flag as proof of completed authorization and skip real security checks.

Content

No source excerpt is available for this finding.