Back to skill

Security audit

差旅费用自动报销助手

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent travel reimbursement purpose, but it combines mailbox access, untrusted link downloads, local sensitive-data processing, and automated financial submission without enough scoping or confirmation.

Review this skill carefully before installing. It should only be used in an environment where the agent is allowed to read the relevant mailbox, download invoice links, process personal travel and invoice data, and submit to FOL. Ask the publisher to add explicit pre-upload and pre-submit confirmations, narrow mailbox/date/sender filters, URL allowlisting, safer filename handling, file size/type validation, and clear retention/deletion rules.

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
references/url_downloader.py:94
Finding

Unrestricted URL Download Enables Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: references/url_downloader.py:94-110, with the unvalidated URL reaching the download functions at references/url_downloader.py:427-477
Vulnerability Type: Server-Side Request Forgery
Risk Level: High

Vulnerable Code

python
def curl_download(url: str, save_dir: str, timeout: int = 30):
    temp_path, headers_path = get_unique_temp_path(save_dir, "curl")

    cmd = [
        "curl", "-s", "-L",
        "-o", temp_path,
        "-D", headers_path,
        "-w", "%{http_code}",
        "--max-time", str(timeout),
    ]
    for k, v in HEADERS.items():
        cmd += ["-H", f"{k}: {v}"]

    cmd.append(url)

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout + 5
        )

The unvalidated URL is selected and passed to the network clients as follows:

python
os.makedirs(save_dir, exist_ok=True)

handler = resolve_handler_by_url(url)
if handler:
    print(f"[download_file] detected a dedicated URL pattern")
    return handler(url, save_dir)

if try_curl_first:
    print(f"[curl] attempting direct download: {url[:80]}")
    result = curl_download(url, save_dir)
    if result and is_attachment_file(result):
        return result

if not filename_hint:
    parsed = urlparse(url)
    path_part = parsed.path.split("/")[-1]
    filename_hint = (
        path_part if path_part and "." in path_part else "invoice.pdf"
    )

result = playwright_download(url, save_dir, filename_hint)
if result and is_attachment_file(result):
    return result

Technical Analysis

The Skill extracts URLs from email bodies and passes them directly to curl and, if the direct download fails, Playwright. It does not:

  • Restrict URLs to HTTPS.
  • Allowlist trusted invoice-provider domains.
  • Resolve and inspect the destination IP address.
  • Block loopback, private, link-local, multicast, or reserved address ranges.
  • Vali ...[truncated 2247 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only https URLs and reject URLs containing credentials, unusual ports, fragments, or unsupported schemes.
  2. Maintain an explicit allowlist of exact invoice-provider hostnames rather than accepting arbitrary email-supplied destinations.
  3. Resolve the hostname before connecting and reject every resolved loopback, private, link-local, multicast, unspecified, and reserved address.
  4. Revalidate the hostname and resolved IP address after every redirect. Do not rely on curl -L to follow redirects without application-level checks.
  5. Restrict curl explicitly, for example with protocol and redirect-protocol controls, while still performing application-level destination validation.
  6. Defend against DNS rebinding by binding the validated hostname to the validated address or using a controlled outbound proxy.
  7. Apply equivalent routing restrictions to Playwright, including request interception that aborts navigation and subresource requests to prohibited destinations.
  8. Apply strict response size, content type, and timeout limits.
  9. Log rejected destinations without recording sensitive URL query parameters.

T09 · Insecure Skill Coding Practices

Error
Location
references/url_downloader.py:123
Finding

Server-Controlled Filename Allows Path Traversal and Arbitrary File Replacement

Content
View full analysis

Vulnerability Details

File Location: references/url_downloader.py:123-161
Vulnerability Type: Path Traversal and Unsafe File Write
Risk Level: High

Vulnerable Code

python
filename = None
if os.path.exists(headers_path):
    with open(
        headers_path,
        "r",
        encoding="utf-8",
        errors="ignore"
    ) as f:
        headers_content = f.read()
        for line in headers_content.split("\n"):
            if "Content-Disposition" in line:
                import re
                match = re.search(
                    r'filename\*=\s*UTF-8\'\'([^\s;]+)',
                    line
                )
                if match:
                    filename = match.group(1)
                else:
                    match = re.search(
                        r'filename=\s*"?([^"\s;]+)"?',
                        line
                    )
                    if match:
                        filename = match.group(1)
                if filename:
                    break

    os.remove(headers_path)

if not filename:
    parsed = urlparse(url)
    path_part = parsed.path.split("/")[-1]
    filename = (
        path_part if path_part and "." in path_part else "invoice.pdf"
    )

final_path = os.path.join(save_dir, filename)
if final_path != temp_path:
    if os.path.exists(final_path):
        os.remove(final_path)
    os.rename(temp_path, final_path)
else:
    final_path = temp_path

Technical Analysis

The downloader trusts the filename or filename* parameter supplied by the remote server in the Content-Disposition response header. The value is joined directly to save_dir without:

  • Reducing it to a basename.
  • Rejecting directory separators.
  • Rejecting absolute paths.
  • Normalizing and verifying the resulting path.
  • Ensuring the resolved destination remains inside save_dir.
  • Preventing replacement of an existing file.

If the filename is absolute, os.path.join() can discard the intended sa ...[truncated 2013 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not use remote filenames as filesystem paths. Generate a random local filename and retain the remote name only as metadata.
  2. If a remote filename must be used, decode it safely and reduce it to a basename using Path(filename).name.
  3. Reject absolute paths, parent-directory components, path separators, control characters, null bytes, device names, and platform-specific alternate separators.
  4. Resolve both save_dir and the candidate destination, then verify that the destination is a child of the resolved save directory.
  5. Use exclusive file creation or an atomic no-clobber operation rather than deleting an existing destination.
  6. Avoid check-then-use path handling that can be affected by symlink races.
  7. Open the destination relative to a trusted directory descriptor with no-follow protections where the operating system supports them.
  8. Apply the same filename controls to Playwright's suggested_filename and files moved from Chrome's default download directory.

T09 · Insecure Skill Coding Practices

Warning
Location
references/url_downloader.py:183
Finding

Weak Attachment Validation Allows Arbitrary and Oversized Content into the Processing Pipeline

Content
View full analysis

Vulnerability Details

File Location: references/url_downloader.py:183-215
Vulnerability Type: Insufficient File-Type and Download-Size Validation
Risk Level: Medium

Vulnerable Code

python
def is_attachment_file(path: str) -> bool:
    """Determine whether a file is an attachment rather than an HTML page."""
    if not os.path.exists(path) or os.path.getsize(path) == 0:
        return False

    try:
        with open(path, "rb") as f:
            header = f.read(512)

            html_signatures = [
                b"<!DOCTYPE",
                b"<html",
                b"<HTML",
                b"<!doctype",
                b"<head",
                b"<HEAD",
                b"<body",
                b"<BODY",
            ]

            for sig in html_signatures:
                if sig in header:
                    return False

            return True
    except Exception:
        return False

The generic download path accepts content based on this permissive check:

python
result = curl_download(url, save_dir)
if result and is_attachment_file(result):
    print(
        f"[curl] download succeeded: {result} "
        f"({os.path.getsize(result)} bytes)"
    )
    return result

Technical Analysis

The function treats any nonempty file as a valid attachment unless one of a small number of case-sensitive HTML signatures appears within the first 512 bytes. It does not require valid PDF or OFD content, despite the reimbursement workflow being designed around those formats.

An attacker can bypass the check with arbitrary binary data, text that lacks the selected tags, HTML using different capitalization or leading content, or malformed document data. The downloader also lacks a maximum response size. The curl command limits elapsed time but does not limit the number of downloaded bytes.

Although an is_pdf_file() helper exists elsewhere in the module, the generic acceptance path uses is_attachment_file() i ...[truncated 1637 chars]

Remediation
View remediation

Remediation Suggestions

  1. Set a strict maximum download size in both curl and Playwright paths and abort streams that exceed it.
  2. Accept only required document formats, using trusted magic-byte inspection rather than filename extensions or negative HTML detection.
  3. For PDFs, require the PDF signature and validate the complete structure with a maintained parser before OCR.
  4. Validate OFD files according to their expected container and internal structure.
  5. Require consistency among the approved content type, detected file type, and locally assigned extension.
  6. Generate local filenames and extensions from the detected type rather than trusting the URL or server-provided filename.
  7. Process untrusted documents in a sandbox with CPU, memory, file-size, execution-time, and filesystem limits.
  8. Keep PDF rendering and OCR dependencies patched and isolate them from credentials and sensitive host paths.
  9. Quarantine files that fail validation and do not pass them to OCR or financial-system upload stages.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
调用 `url_downloader.py` 下载:

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
调用 `url_downloader.py` 下载:

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill proceeds to bulk upload invoices and submit reimbursement forms to a financial system, yet the description lacks strong warnings and explicit approval checkpoints for these state-changing actions. In this context, automated financial submission is especially dangerous because mistakes can create fraudulent, duplicate, or incorrect expense claims and may trigger compliance or accounting issues.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill performs sensitive actions including mailbox access, attachment/link downloading, local file writes, shell-based CLI execution, network retrieval, and automated submission to a finance system, but it declares no explicit tool or permission scope. Without an allowlist and least-privilege boundaries, an agent may invoke broader capabilities than users expect, increasing the blast radius of prompt injection, mailbox-borne malicious content, or workflow mistakes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill is designed to search a user's email, read message contents, and automatically download invoice attachments or follow invoice links, but the description does not clearly warn users before those privacy-sensitive actions occur. This can lead to unanticipated access to mailbox contents and retrieval of attacker-controlled files or URLs embedded in email, which is especially risky because email is an untrusted input source.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill automatically uploads invoice files to the finance system and submits reimbursement forms, yet the description does not clearly disclose that it may perform these irreversible or high-consequence external actions. In this context, silent automation is dangerous because incorrect OCR extraction, wrong recipient invoices, or maliciously crafted email content could result in erroneous financial submissions or disclosure of sensitive documents to downstream systems.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The flow explicitly automates searching the user's mailbox, downloading attachments, and following invoice links, but it does not require clear user consent, scope limitation, or warnings about accessing potentially sensitive email content. This creates a privacy and overreach risk because the skill may access more messages and data than the user expects, especially when it falls back to broad searches of recent mail.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The workflow writes invoice data, travel schedules, reimbursement details, and history into local directories and memory without documenting retention, sensitivity handling, or user approval. Because reimbursement records can contain personal, financial, and travel information, silent persistence increases the risk of unintended disclosure, cross-task leakage, and excessive data retention.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The guide instructs bulk OCR over all uploaded PDFs and explicitly outputs full extracted text and absolute file paths, which can expose invoice contents, personal data, and internal filesystem structure to downstream components or logs. In a travel reimbursement skill, those PDFs are likely to contain sensitive financial and identity information, so documenting indiscriminate extraction without minimization or privacy controls creates a real data-handling risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The guide directs extraction of passenger names, masked ID numbers, buyer name, and tax identifiers from train-ticket OCR results without any privacy notice, data-minimization guidance, or handling restrictions. Because this skill is specifically designed for expense reimbursement workflows, the collection and propagation of personally identifiable and corporate billing data is contextually more dangerous: the data is not incidental but central to the workflow and likely to be processed at scale.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · references/url_downloader.py (reported line 121)May include surrounding context.

python
cmd.append(url)

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 5)
        http_code = result.stdout.strip() if result.stdout else ""

        if http_code != "200" or not os.path.exists(temp_path) or os.path.getsize(temp_path) == 0:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The guidance explicitly instructs the agent to read invoice data and generate travel schedule and reimbursement output files that contain sensitive personal and business travel information, but it provides no privacy guardrails, minimization requirements, consent checks, or retention limits. In the context of an automated reimbursement skill that processes invoices, tickets, hotels, and email attachments, this increases the risk of unnecessary exposure or persistence of personal itinerary, lodging, and expense data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The entire skill description and user-facing interaction examples are written only in Chinese, with no indication that another language can be used or selected. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern unless the locale restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The hard-coded header "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8" makes the skill prefer a specific language/locale for all downloads. This is a natural-language policy concern because the file does not provide any user-selectable locale behavior or justification for forcing Chinese as the default.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.