Back to skill

Security audit

Invoice Forge

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local invoice tool, but it needs Review because generated invoices can include unescaped active HTML and custom invoice numbers can write files outside the intended output folder.

Review before installing or using with untrusted client data. Treat generated HTML invoices as active browser content until escaping is fixed, avoid custom invoice numbers from untrusted sources, run tests only in an isolated folder, and do not expose the data directory through cloud sync, SSH, or a server without separate access controls and backups.

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

T09 · Insecure Skill Coding Practices

Error
Location
invoice_template.py:99
Finding
Stored HTML and Script Injection in Generated Invoices<![CDATA[ ## Vulnerability Details **File Location**: `invoice_template.py:99-113`; additional unescaped HTML sinks occur at `invoice_template.py:392-456` **Vulnerability Type**: Stored HTML injection / stored cross-site scripting **Risk Level**: High ### Vulnerable Code ```python # Build items HTML items_html = "" for item in items: desc = item.get("description", "") qty = item.get("quantity", 0) rate = item.get("rate", 0.0) amount = item.get("amount", 0.0) items_html += f""" <tr> <td>{desc}</td> <td class="text-right">{qty}</td> <td class="text-right">{fmt(rate)}</td> <td class="text-right amount">{fmt(amount)}</td> </tr> """ ``` The completed HTML document also directly interpolates other values without escaping: ```python <div class="business-info"> {logo_html} <h1>{business['name']}</h1> <p>{business['address'].replace(chr(10), '<br>')}</p> <p>{business['email']}</p> <p>{business['phone']}</p> {f"<p>{business['website']}</p>" if business['website'] else ""} </div> <div class="invoice-info"> <h2>INVOICE</h2> <p><strong>Invoice #:</strong> {invoice_number}</p> <p><strong>Date:</strong> {invoice_date}</p> <p><strong>Due Date:</strong> {due_date}</p> <span class="status-badge {status_class}">{status_text}</span> </div> ``` ```python <div class="party"> <h3>Bill To</h3> <p><strong>{client.get('name', '')}</strong></p> <p>{client.get('email', '')}</p> {f"<p>{client.get('address', '').replace(chr(10), '<br>')}</p>" if client.get('address') else ""} {f"<p>{client.get('phone', '')}</p>" if client.get('phone') else ""} </div> ``` ```python {f'''<div class="payment-details"> <h3>Payment Details</h3> <pre>{payment_details}</pre> </div>''' if payment_details else ''} {f'<div class="notes"><strong>Notes:</strong> {notes}</div>' if notes else ''} {f'<div class="terms"><strong>Terms:</strong> {terms}</div>' if terms else ''} ...[truncated 2711 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML escaping to every value inserted as text: ```python from html import escape def html_text(value) -> str: return escape(str(value), quote=True) ``` Use this helper for invoice numbers, descriptions, client fields, business fields, notes, terms, payment details, tax labels, discount information, and statuses. 2. Preserve intended line breaks only after escaping: ```python safe_address = html_text(address).replace("\n", "<br>") ``` Do not replace newlines before escaping, because that would also escape the intentionally inserted `<br>` elements. 3. Validate website and link values separately. Permit only explicitly supported schemes, such as `https`, rather than treating arbitrary configuration values as safe markup. 4. Prefer a maintained template engine with automatic HTML escaping if adding a dependency is acceptable. Keep automatic escaping enabled and use explicit safe-markup annotations only for internally generated markup. 5. Consider adding a restrictive Content Security Policy to generated HTML, for example one that blocks scripts and limits external resources. This is defense in depth and does not replace proper output encoding. 6. Add regression tests using payloads containing: ```text <script>alert(1)</script> <img src=x onerror=alert(1)> "><svg onload=alert(1)> & < > " ' ``` The tests should verify that these values appear as encoded text and do not create executable elements or attributes. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
invoice_forge.py:351
Finding
Path Traversal and File Overwrite Through Unsanitized Invoice Numbers<![CDATA[ ## Vulnerability Details **File Location**: `invoice_forge.py:351-373` **Vulnerability Type**: Path traversal / arbitrary file write **Risk Level**: High ### Vulnerable Code ```python invoice_number = invoice_data.get("invoice_number", "invoice") extensions = { "html": ".html", "markdown": ".md", "text": ".txt", } ext = extensions.get(format, ".txt") filename = f"{invoice_number}{ext}" filepath = os.path.join(self.output_dir, filename) # Backup if exists if os.path.exists(filepath) and self.config.get("BACKUP_ON_OVERWRITE", True): backup = f"{filepath}.bak" if os.path.exists(backup): os.remove(backup) os.rename(filepath, backup) # Render and save content = self.render_invoice(invoice_data, format) with open(filepath, "w", encoding="utf-8") as f: f.write(content) return filepath ``` The public API permits a custom invoice number: ```python def create_invoice( self, client_id: str, items: List[Dict], invoice_date: Optional[str] = None, payment_terms: Optional[int] = None, tax_type: Optional[str] = None, discount: Optional[Dict] = None, notes: str = "", status: str = "pending", invoice_number: Optional[str] = None, ) -> Dict: ``` ```python if invoice_number is None: invoice_number = self._generate_invoice_number() ``` ### Technical Analysis `save_rendered_invoice()` treats `invoice_data["invoice_number"]` as a filename component without validating it. `os.path.join()` does not prevent traversal when the filename contains `../` segments, and it does not guarantee that the normalized destination remains under `self.output_dir`. For example, with an output directory of `output`, an invoice number of: ```text ../../target ``` produces: ```text output/../../target.html ``` The operating system resolves the parent-directory segments when opening the path, allowing the write to escape the configured invoice directory. If the destination already exists and backups a ...[truncated 2298 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict invoice numbers to a conservative filename-safe format: ```python import re INVOICE_NUMBER_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$") if not INVOICE_NUMBER_PATTERN.fullmatch(str(invoice_number)): raise ValueError("Invoice number contains unsupported characters") ``` 2. Reject path separators, parent-directory segments, absolute paths, null bytes, and platform-specific separators. An allowlist is safer than attempting to remove dangerous characters. 3. Resolve and verify the final destination before any existence check, deletion, rename, or write: ```python output_root = os.path.realpath(self.output_dir) candidate = os.path.realpath(os.path.join(output_root, filename)) if os.path.commonpath([output_root, candidate]) != output_root: raise ValueError("Invoice output path escapes OUTPUT_DIR") ``` 4. Apply the containment check to both the destination and backup path. 5. Generate filesystem names independently from display invoice numbers where possible. For example, retain the original invoice number inside the rendered document but derive the output filename from a sanitized identifier. 6. Use atomic writes: create a temporary file inside the verified output directory, flush and synchronize it, and then replace the destination with `os.replace()`. 7. Add tests for Unix and Windows traversal patterns, including: ```text ../target ../../target ..\target C:\target /absolute/path invoice/subdirectory ``` Tests should confirm that every resulting file remains under the configured output directory and that invalid invoice numbers are rejected before backup operations occur. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Hidden Instructions

High
Category
Prompt Injection
Content
---
name: "Invoice Forge Professional Invoice Generator"
description: "Professional invoice generation for freelancers and small businesses. Generate beautiful invoices in seconds. No dependencies, no subscriptions, no cloud required. Clean Python code."
author: "@TheShadowRose"
Confidence
60% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
---
name: "Invoice Forge Professional Invoice Generator"
description: "Professional invoice generation for freelancers and small businesses. Generate beautiful invoices in seconds. No dependencies, no subscriptions, no cloud required. Clean Python code."
author: "@TheShadowRose"
Confidence
60% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
The limitations above are **design choices**, not bugs:
- No database = no migrations, no corruption
- No cloud = no downtime, no data breaches
- No auth = no password resets, no account recovery
- No framework = no bloat, no security patches

If you need more features, fork it! The code is MIT-licensed and easy to extend.
Confidence
75% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This test script performs non-trivial persistent state changes: it adds a client, creates invoices, updates invoice status, and writes output/data files, yet it gives no warning, isolation, or cleanup. Running it against a real or shared environment can pollute business records, overwrite assumptions in later runs, or cause users to mistake test artifacts for legitimate data.

Missing User Warnings

Low
Confidence
85% confidence
Finding
The documentation recommends Dropbox/Google Drive sync, SSH access, and server deployment as workarounds without clearly warning about the security implications of exposing invoice data to third-party storage or remote access paths. Users may adopt these suggestions without implementing encryption, access restrictions, or authentication, increasing the risk of data exposure or unauthorized access.

Intent-Code Divergence

Low
Confidence
78% confidence
Finding
The philosophy section states 'No auth' as a defining property, but elsewhere the document explicitly suggests SSH for remote access and mentions adding your own authentication when running on a server. That creates an intent-level contradiction in the documentation about whether authentication is absent as part of the system model or merely not built into the tool itself.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
The configuration examples set `CURRENCY_SYMBOL = "$"` and `CURRENCY_CODE = "USD"` as defaults, which nudges users toward a specific locale/currency without any explicit opt-in or note that these are only examples. Under the policy, locale-specific defaults should either offer a user choice or be clearly justified as region-specific.

Scope Creep

Low
Category
Excessive Agency
Content
**USE AT YOUR OWN RISK.**

- The author(s) are NOT liable for any damages, losses, or consequences arising from 
  the use or misuse of this software — including but not limited to financial loss, 
  data loss, security breaches, business interruption, or any indirect/consequential damages.
- This software does NOT constitute financial, legal, trading, or professional advice.
- Users are solely responsible for evaluating whether this software is suitable for
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Scope Creep

Low
Category
Excessive Agency
Content
**USE AT YOUR OWN RISK.**

- The author(s) are NOT liable for any damages, losses, or consequences arising from 
  the use or misuse of this software — including but not limited to financial loss, 
  data loss, security breaches, business interruption, or any indirect/consequential damages.
- This software does NOT constitute financial, legal, trading, or professional advice.
- Users are solely responsible for evaluating whether this software is suitable for
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The generated HTML hard-codes the document language as English, which is a natural-language locale choice applied to all rendered invoices. The file does not offer a way for callers to select another locale or document why English is required, so this conflicts with the policy against forcing a language without opt-in.

Static analysis

No suspicious patterns detected.