Back to skill

Security audit

unified-invoice

Security checks for vulnerabilities and agentic risk

Overview

This invoice skill is mostly purpose-aligned, but it has real safety issues around unescaped document data, path handling, and sensitive business data storage.

Review before installing or using with real customer data. Keep the data directory and generated invoices out of shared repos, avoid untrusted client/item/notes/date values, and prefer running PDF conversion only in an isolated browser profile 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.js:92
Finding
Unescaped Invoice Data Allows HTML and Script Injection During PDF Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js:92-133`, `scripts/generate.js:181-210`, and `scripts/generate.js:232-241` **Vulnerability Type**: HTML injection into a browser-rendered document **Risk Level**: High ### Vulnerable Code ```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 path 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 || ''}</td> </tr> `; }); const html = template . ...[truncated 3425 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply HTML escaping to every dynamic text value before inserting it into a template, including supplier data, client data, item names, units, notes, dates, and invoice types. 2. Replace manual string concatenation and `.replace()` templating with a maintained template engine that enables automatic escaping by default. 3. Treat `itemRows` as structured data rather than preassembled HTML. Generate rows through safe template iteration. 4. Validate fields according to their expected formats. For example, restrict invoice type to an explicit allowlist and enforce maximum lengths on names and notes. 5. Configure the rendering page defensively: - Disable JavaScript when it is not required. - Block HTTP and HTTPS requests using Puppeteer request interception. - Reject frames, scripts, plugins, and remote resources. - Use a dedicated, isolated browser profile with no authenticated sessions or unnecessary permissions. 6. Add tests using payloads such as `<script>`, `<img onerror>`, attribute-breaking strings, and malicious CSS to verify that generated output renders them only as text. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.js:135
Finding
Unvalidated Date Values Permit Output Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js:21-27`, `scripts/generate.js:135-138`, `scripts/generate.js:212-215`, `scripts/generate.js:273-274`, and `scripts/generate.js:292-293`; also `scripts/freelance-run.sh:35-36` and `scripts/freelance-run.sh:58-60` **Vulnerability Type**: Path traversal and file overwrite **Risk Level**: High ### Vulnerable Code The date formatter accepts any string without validation: ```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 supplied value is used in output filenames: ```js const issueDate = formatDate(options.issueDate); const validUntil = formatDate(options.validUntil || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)); 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 equivalent: ```js const issueDate = formatDate(options.issueDate); const approvalNumber = `T${Date.now().toString().slice(-10)}`; 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 command-line parser accepts the values directly: ```js else if (args[i] === '--issue-date') options.issueDate = args[++i]; else if (args[i] === '--valid-until') options.validUntil = args[++i]; ``` ```js else if (args[i] === '--issue-date') options.issueDate = args[++i]; ``` The shell implementation has the same weakness: ```bash --date) DATE="$2" shift 2 ;; ``` ```bash CLIENT_SAFE=$(echo "$CLIENT" | tr -d ' ') FILENAME="$INVOICES_DIR/${DATE}-${CLIENT_SAFE ...[truncated 2004 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse dates strictly and require the exact `YYYY-MM-DD` format. 2. Verify that each accepted value represents a real calendar date rather than relying only on a regular expression. 3. Construct filenames exclusively from the normalized output of the parsed date. 4. Sanitize every filename component, including client names, by replacing path separators and control characters with safe characters. 5. Resolve and verify the final destination before writing: ```js const base = path.resolve(OUTPUT_DIR); const destination = path.resolve(base, safeFilename); if (destination !== base && !destination.startsWith(base + path.sep)) { throw new Error('Output path escapes the configured output directory'); } ``` 6. Use `path.basename()` as an additional defense for individual filename components. 7. Use exclusive file creation where overwriting is not intended, or require explicit confirmation before replacing an existing document. 8. In the shell script, validate with a strict pattern, reject `/`, `\`, `..`, control characters, and newlines, and verify the canonical destination remains under `INVOICES_DIR`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/freelance-run.sh:53
Finding
Unvalidated Amount Is Interpreted as a bc Program and Injected into Event JSON<![CDATA[ ## Vulnerability Details **File Location**: `scripts/freelance-run.sh:27-30`, `scripts/freelance-run.sh:53-54`, and `scripts/freelance-run.sh:116-126` **Vulnerability Type**: Expression injection, denial of service, and malformed JSON injection **Risk Level**: Medium ### Vulnerable Code ```bash --amount) AMOUNT="$2" shift 2 ;; ``` The value is embedded directly into programs evaluated by `bc`: ```bash # 부가세 계산 (10%) VAT=$(echo "scale=0; $AMOUNT * 0.1 / 1" | bc) TOTAL=$(echo "$AMOUNT + $VAT" | bc) ``` The original value and calculated result are also inserted into JSON without serialization or quoting: ```bash EVENT_FILE="$EVENTS_DIR/invoice-generated-$(date +%s).json" cat > "$EVENT_FILE" << EOF { "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "source": "invoice-gen", "invoice_num": "$INVOICE_NUM", "client": "$CLIENT", "amount": $AMOUNT, "total": $TOTAL, "file": "$FILENAME" } EOF ``` ### Technical Analysis `AMOUNT` is expected to be a monetary number but is never validated. Shell expansion places it inside a textual program that is passed to `bc`. Operators, statement separators, loops, function constructs, and other supported syntax can therefore change the intended computation or cause excessive processing. This is expression-language injection rather than direct shell command injection: shell metacharacters introduced by parameter expansion are not reparsed as shell syntax in this context. Nevertheless, the attacker can manipulate `bc` behavior, invalidate invoice totals, cause the script to fail, or consume excessive resources. The same untrusted value is inserted raw into an event JSON document. Newlines, commas, braces, or other tokens can produce invalid JSON or inject additional JSON structure. The `CLIENT` and `FILENAME` string values are also not JSON-escaped, increasing the event-integrity risk. ### Attack Path 1. An attacker or untrusted caller supplies a crafted `--amount` value containing `bc` expression syn ...[truncated 933 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `AMOUNT` before any calculation. Accept only a bounded numeric representation appropriate for the supported currency, for example non-negative integers in minor units. 2. Reject signs, operators, whitespace, newlines, exponent notation, statement separators, and all nonnumeric characters unless explicitly required. 3. Enforce sensible minimum and maximum amounts to prevent overflow and resource abuse. 4. Avoid interpreting user input as a `bc` program. Prefer fixed integer arithmetic in minor units or pass only a previously validated numeric literal. 5. Generate event files using a real JSON serializer, such as a small Node.js helper, rather than interpolating values into a heredoc. 6. JSON-escape all string fields, including client names and file paths. 7. Validate calculated results before writing invoices or events, and abort safely if any computation produces invalid or unexpected output. 8. Add tests for semicolons, newlines, braces, quotes, very large values, negative values, and computationally expensive expressions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (39)

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
설명은 통합 견적서/세금계산서 생성기로서 한국형 견적서, 프리랜서 인보이스, 거래처/품목 DB, PDF 출력, 자동 계산을 포함한다고 주장한다. 그러나 실제 코드는 CLI 기반으로 단일 마크다운 인보이스만 생성하며, 입력된 서비스/고객/금액을 템플릿에 채우고 10% VAT를 계산하는 수준이다. 사업자등록번호 처리, 세금계산서 양식 생성, 견적서 생성, 거래처/품목 데이터베이스, PDF 변환은 구현되어 있지 않다. 또한 코드에는 설명에 없는 이벤트 디렉토리에 JSON 이벤트를 발행하는 동작이 포함된다. 자동 계산 자체는 일부 부합하지만, 핵심 기능 다수가 과장 또는 누락되어 있어 설명과 실제 동작은 materially mismatch이다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
설명과 실제 구현은 상당 부분 일치한다. 실제로 거래처/품목 JSON DB를 사용하고, 견적서와 세금계산서를 자동 계산하여 HTML 및 PDF로 생성한다. 한국형 사업자등록번호/부가세 처리도 템플릿 변수와 VAT 계산에서 반영된다. 다만 설명에 포함된 '프리랜서 인보이스(다국어, VAT)' 기능은 이 코드 조각에서 확인되지 않는다. 별도의 invoice 명령, 다국어 템플릿 선택, 언어 옵션, 해외 인보이스용 필드 처리 등이 없으므로 설명이 실제 구현 범위를 과장하고 있다. 따라서 부분적 설명 불일치로 판단된다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
설명에는 문서 생성기 전체 기능이 포함되어 있으나, 실제 코드 조각은 그중 일부인 거래처 데이터 관리만 수행한다. 거래처 DB 관리는 선언된 목적의 보조 구성요소로 볼 수 있지만, 현재 코드의 주된 역할은 CRUD형 클라이언트 관리이며 선언된 핵심 기능인 견적서/세금계산서 생성, 다국어 인보이스, PDF 출력, 자동 세금 계산은 구현되어 있지 않다. 따라서 이 코드 조각만 기준으로 보면 선언된 설명과 실제 동작 사이에 중요한 범위 차이가 있어 mismatch로 판단된다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
제공된 코드는 품목 데이터베이스의 일부인 items.json을 관리하는 보조 CLI 도구에 해당합니다. 품목 추가, 수정, 삭제, 조회, 목록 출력 기능은 선언된 '품목 DB' 범주와 일부 일치할 수 있으나, 선언된 핵심 목적은 통합 견적서/세금계산서 생성입니다. 실제 코드에는 견적서/인보이스 문서 생성, 세금 계산, VAT/부가세 처리, 사업자등록번호 처리, 다국어 출력, PDF 생성, 거래처 정보 관리 등의 핵심 기능이 전혀 없습니다. 따라서 이 코드 청크의 실제 동작은 선언된 설명의 전체 목적에 비해 현저히 축소되어 있으며, 설명을 정확히 대표하지 못하므로 mismatch로 판단됩니다.

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js quote \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-clients.js add "무펭이즘" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/manage-items.js add "포토부스 대여" \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs users to store sensitive business and personal data such as business registration numbers, addresses, phone numbers, email addresses, and bank-account details in local JSON files, but it provides no security guidance on access control, encryption, redaction, retention, or safe sharing. In an invoicing context, this raises the risk of privacy leakage, financial fraud, and accidental exposure through source control, backups, generated documents, or multi-user environments.

Static analysis

No suspicious patterns detected.