Back to skill

Security audit

Korean Invoice

Security checks for vulnerabilities and agentic risk

Overview

This Korean invoice skill is mostly purpose-aligned, but it has real safety issues around unsafe HTML/PDF rendering, path handling, and sensitive business data storage.

Review before installing. Use it only with trusted invoice data, avoid putting this skill directory in shared or synced repositories, protect the data/*.json and output files, and update dependencies. Do not let untrusted parties control client names, item names, notes, or issue dates until HTML escaping and output-path validation are fixed.

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
scripts/generate.js:96
Finding
Unescaped Invoice Data Allows HTML and Script Injection During PDF Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js:96-127` and `scripts/generate.js:197-223` **Vulnerability Type**: HTML injection leading to browser-side script execution **Risk Level**: High ### Vulnerable Code Quotation item data and template variables are inserted directly into HTML: ```js let itemRows = ''; itemList.forEach((item, idx) => { const amount = item.quantity * item.price; itemRows += ` <tr> <td>${idx + 1}</td> <td>${item.name}</td> <td>${item.unit}</td> <td>${formatCurrency(item.quantity)}</td> <td>${formatCurrency(item.price)}</td> <td>${formatCurrency(amount)}</td> </tr> `; }); const html = template .replace(/{{myCompanyName}}/g, myInfo.companyName || '') .replace(/{{myBusinessNumber}}/g, myInfo.businessNumber || '') .replace(/{{myCEO}}/g, myInfo.ceo || '') .replace(/{{myAddress}}/g, myInfo.address || '') .replace(/{{myPhone}}/g, myInfo.phone || '') .replace(/{{myEmail}}/g, myInfo.email || '') .replace(/{{clientName}}/g, client.name || '') .replace(/{{clientBusinessNumber}}/g, client.businessNumber || '') .replace(/{{clientCEO}}/g, client.ceo || '') .replace(/{{clientAddress}}/g, client.address || '') .replace(/{{clientPhone}}/g, client.phone || '') .replace(/{{issueDate}}/g, issueDate) .replace(/{{validUntil}}/g, validUntil) .replace(/{{itemRows}}/g, itemRows) .replace(/{{subtotal}}/g, formatCurrency(subtotal)) .replace(/{{vat}}/g, formatCurrency(vat)) .replace(/{{total}}/g, formatCurrency(total)) .replace(/{{notes}}/g, options.notes || ''); ``` The tax-invoice generator has the same issue: ```js let itemRows = ''; itemList.forEach((item, idx) => { const amount = item.quantity * item.price; itemRows += ` <tr> <td>${issueDate}</td> <td>${item.name}</td> <td>${formatCurrency(item.quantity)}</td> <td>${formatCurrency(item.price)}</td> <td>${formatCurrency(amount)}</td> <td>${options.notes ...[truncated 3315 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Introduce a centralized HTML-escaping function that encodes at least `&`, `<`, `>`, `"`, and `'`. 2. Escape every scalar value before inserting it into an HTML text context, including supplier fields, client fields, item names, units, notes, dates, and invoice type. 3. Replace manual string concatenation and chained `replace()` calls with a template engine that enables automatic escaping by default. 4. Keep trusted template fragments, such as generated table-row structure, separate from untrusted text values. 5. Validate fields according to strict schemas: - Dates must match the expected date format. - Invoice type should be selected from an allowlist. - Numeric fields must be finite integers within acceptable business limits. - Text fields should have reasonable length limits. 6. Disable JavaScript while rendering invoices if scripts are not required: ```js await page.setJavaScriptEnabled(false); ``` 7. Add a restrictive Content Security Policy to both templates, for example one that denies scripts and external connections. 8. Consider request interception in Puppeteer and reject all network requests not required for local invoice rendering. 9. Add tests using payloads containing closing tags, script elements, event-handler attributes, and malformed table markup to verify that they are rendered only as text. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.js:32
Finding
Unvalidated Issue Date Allows Output Path Traversal and File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js:32-34`, `scripts/generate.js:130-133`, `scripts/generate.js:225-228`, `scripts/generate.js:275`, and `scripts/generate.js:294` **Vulnerability Type**: Path traversal through an attacker-controlled output filename **Risk Level**: Medium ### Vulnerable Code String dates are returned without validation or normalization: ```js function formatDate(date) { if (typeof date === 'string') return date; const d = date || new Date(); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } ``` The value is incorporated directly into quotation output paths: ```js const filename = `${issueDate}-견적서-${client.name}`; const htmlPath = path.join(OUTPUT_DIR, `${filename}.html`); fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.writeFileSync(htmlPath, html, 'utf-8'); ``` The tax-invoice path is constructed identically: ```js const filename = `${issueDate}-세금계산서-${client.name}`; const htmlPath = path.join(OUTPUT_DIR, `${filename}.html`); fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.writeFileSync(htmlPath, html, 'utf-8'); ``` The untrusted date is read directly from the command line: ```js else if (args[i] === '--issue-date') options.issueDate = args[++i]; ``` ### Technical Analysis The `--issue-date` option is expected to contain a date, but no date-format validation is performed. Any string is returned unchanged by `formatDate()` and then becomes part of an output filename. If the value contains path separators and traversal segments such as `../`, `path.join()` normalizes those components. Consequently, the resolved destination can escape `OUTPUT_DIR`. The generated suffix remains constrained by the application's filename construction, but this does not prevent creation or replacement of matching `.html` and, when PDF generation is enabled, `.pdf` files outside th ...[truncated 1756 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Strictly validate `--issue-date` against the required `YYYY-MM-DD` format. 2. Parse the date and confirm that it represents a real calendar date rather than relying only on a regular expression. 3. Reject all filename inputs containing `/`, `\`, null bytes, `.` traversal components, or platform-specific separator characters. 4. Sanitize client names before using them in filenames. Prefer a generated identifier or a conservative allowlist such as letters, digits, spaces, underscores, and hyphens. 5. Resolve and verify every output destination before writing: ```js const outputRoot = path.resolve(OUTPUT_DIR); const destination = path.resolve(outputRoot, `${filename}.html`); if ( destination !== outputRoot && !destination.startsWith(outputRoot + path.sep) ) { throw new Error('Invalid output path'); } ``` 6. Perform the same containment validation independently for HTML and PDF paths. 7. Use non-overwriting file creation where replacement is not intended, or explicitly require confirmation before overwriting an existing document. 8. Add tests for `../`, absolute paths, backslash traversal, mixed separators, encoded separators, and malicious client names. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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 (23)

Known Vulnerable Dependency: basic-ftp==5.1.0 — 4 advisory(ies): CVE-2026-27699 (Basic FTP has Path Traversal Vulnerability in its downloadToDir() method); GHSA-6v7q-wjvx-w8wg (basic-ftp: Incomplete CRLF Injection Protection Allows Arbitrary FTP Command Exe); CVE-2026-41324 (basic-ftp vulnerable to denial of service via unbounded memory consumption in Cl) +1 more

Critical
Category
Supply Chain
Confidence
91% confidence
Finding
The lockfile pins basic-ftp 5.1.0, which is reported with multiple serious advisories including path traversal, FTP command injection, and DoS conditions. In this package it appears only as a transitive dependency via get-uri/proxy-agent/puppeteer tooling, so exploitability depends on whether attacker-controlled FTP or proxy URLs are ever processed, but shipping a known-vulnerable version is still a real supply-chain risk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
설명은 문서 생성 및 세금 계산 자동화를 핵심 기능으로 제시하지만, 실제 코드는 거래처명, 사업자등록번호, 대표자, 주소, 연락처, 이메일 등을 clients.json에 저장하고 관리하는 명령행 도구입니다. 사업자등록번호 필드를 저장/표시하긴 하지만 검증이나 활용한 계산은 하지 않으며, 견적서나 세금계산서 파일 생성, 서식 출력, 금액 처리, 부가세 계산 로직이 전혀 없습니다. 따라서 선언된 목적과 실제 동작 사이에 중대한 불일치가 있습니다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
코드는 manage-items.js로, items.json 파일을 사용해 품목 데이터를 CRUD하는 명령줄 스크립트다. add/edit/remove/list/view 명령만 구현되어 있으며, 문서 생성, 세금계산서 발행, 사업자 정보 관리, VAT 계산 등의 로직은 전혀 보이지 않는다. 따라서 선언된 설명과 실제 동작의 핵심 목적이 materially different 하며 명백한 설명-행동 불일치이다.

Known Vulnerable Dependency: extract-zip==2.0.1 — 2 advisory(ies): CVE-2026-19693 (extract-zip allows arbitrary file writes through symlink archive entries); CVE-2026-56876 (extract-zip unvalidated symlink path traversal)

High
Category
Supply Chain
Confidence
95% confidence
Finding
extract-zip 2.0.1 is present with advisories for arbitrary file write via symlink/path traversal during archive extraction. Because this package is used by @puppeteer/browsers to unpack browser downloads, compromise of the archive source or a man-in-the-middle/proxy path could potentially write files outside the intended directory.

Known Vulnerable Dependency: ip-address==10.1.0 — 2 advisory(ies): CVE-2026-69192 (ip-address: Address4 decodes leading-zero octets as decimal while resolvers deco); CVE-2026-42338 (ip-address has XSS in Address6 HTML-emitting methods)

High
Category
Supply Chain
Confidence
84% confidence
Finding
ip-address 10.1.0 is flagged for parsing inconsistencies and XSS in HTML-emitting methods. Here it is a transitive dependency under socks/socks-proxy-agent/proxy support, so unless the skill exposes attacker-controlled IP parsing or renders those HTML helpers, practical impact is limited, but the vulnerable package is still present.

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
93% confidence
Finding
ws 8.19.0 is a confirmed vulnerable WebSocket library version with reported memory disclosure and memory exhaustion issues. Since puppeteer-core depends on ws for browser protocol communication, exposure may exist if the skill connects to untrusted or attacker-influenced WebSocket endpoints, though local-only DevTools use would reduce practical risk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README instructs users to store business registration numbers, addresses, phone numbers, email addresses, and bank account details in local JSON files, but provides no warning about the sensitivity of this data, access controls, or exclusion from version control and backups. In the context of an invoicing skill, this creates a realistic risk of unintended disclosure of business and personal information through repository commits, local compromise, or sharing of the skill directory.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README documents remove commands for clients and items without any confirmation, backup, or irreversibility warning. While this is more of a safety and integrity issue than a classic exploit path, users could easily destroy billing data needed for future invoices or records, especially in an agent-driven or automated environment.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README states that generated quotes and tax invoices are saved into an output directory, but does not warn that these documents may contain customer identifiers, business numbers, addresses, and financial amounts. Because invoice documents are commonly shared, synced, or left in predictable locations, the absence of handling guidance increases the chance of sensitive document exposure.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill documents destructive commands that remove client and item records without any warning about permanence, confirmation, backup, or undo. While not an exploit primitive by itself, it creates a realistic risk of accidental data loss affecting billing records and business operations.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly stores sensitive business and personal information locally, including business registration numbers, addresses, phone numbers, email addresses, and bank account details, but gives no guidance on file permissions, encryption, retention, or secure handling. On shared systems or synced working directories, this can lead to unauthorized disclosure of financial and personally identifiable data.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script persists client business and contact information to a local JSON file without any user-facing notice, consent flow, or explanation of retention. In the context of an invoice skill, this data can include sensitive business identifiers and personal contact details, creating privacy and data-handling risk if users are unaware it is being stored on disk.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest describes a skill for automatically generating Korean quotes and tax invoices with business number and VAT calculation. This script implements full create, edit, delete, list, and view management of a persistent client database, which is a broader customer-management capability rather than an obvious implementation detail of document generation itself.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The removeClient function performs an irreversible update to the persisted client list by deleting a matching record and rewriting the data file. While it logs after deletion, there is no confirmation prompt, pre-action warning, or descriptive comment indicating that data will be removed, so the destructive operation lacks user disclosure.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This code presents all user-facing messages, errors, and usage instructions exclusively in Korean, including the main CLI help text. The file does not offer a language selection mechanism or document that the tool is intentionally Korea-specific, which creates a natural-language locale policy violation under the stated rules.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
All user-facing descriptive text and examples are presented only in Korean, with no indication that users may choose another language or locale. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale restriction is explicitly documented and justified.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
This JSON file contains user-facing product names, units, and descriptions only in Korean, which can indicate a language/locale policy violation when no opt-in, fallback, or documented region-specific scope is present. Because SQP-3 applies to all file types and covers forced language/locale behavior, this is a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The description states the skill is for '한국형' invoice/tax document generation, and the package metadata is entirely Korean-facing. This suggests a fixed language/locale orientation without any visible indication that users can opt into another language or locale, which may conflict with language/locale choice expectations.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "무펭이",
  "license": "MIT",
  "dependencies": {
    "puppeteer-core": "^23.0.0"
  }
}
Confidence
90% confidence
Finding
The dependency uses a caret range (^23.0.0), which permits automatic installation of newer minor and patch releases. This can introduce supply-chain risk because builds may become non-reproducible and could pull in a compromised or breaking upstream version without review.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
This JavaScript file uses Korean-only natural-language comments and user-facing output such as usage text and error messages. Under the language/locale policy rule, forcing a specific language without user opt-in is a policy concern when no alternative locale or selection mechanism is provided.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
Natural-language strings throughout the script, including errors, success messages, empty-state text, and usage instructions, are written only in Korean. Because the file does not provide a language option or explain that the tool is intentionally limited to a Korean-speaking context, this is a language/locale policy violation.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The HTML root sets `lang="ko"`, and the document content is primarily fixed in Korean, which imposes a specific language/locale by default. The file does not indicate user opt-in or explain that the template is intentionally limited to a Korea-specific business context.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The HTML document declares `lang="ko"` and the visible invoice content is written in Korean, which imposes a specific language/locale by default. Under the policy rule, locale-specific behavior should either offer user choice or clearly document why the constraint is required.

Static analysis

No suspicious patterns detected.