Back to skill

Security audit

wm-report-extract

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real financial-report extraction skill, but it needs review because some commands can fetch arbitrary URLs and an unsafe cache-clean path can delete directories outside its cache.

Review before installing. Use this only in a sandbox or disposable environment until path containment and URL controls are fixed. Provide trusted HTTPS PDF links only, avoid untrusted values for cache IDs or --out paths, do not run cache clean on attacker-influenced input, set the cache directory to a safe disposable location, and pin dependencies before production use.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wm_report.py:5393
Finding

Path Traversal Enables Recursive Deletion Outside the Skill Cache

Content
View full analysis
Path: return cache_root() / sha ``` ```python def materialize_tables(sha12: str, *, out_name: str | None, force: bool) -> dict: d = entry_dir(sha12) ... result_name = out_name or f"result-{ts}" result_dir = d / result_name ... if result_dir.exists() and force: ... shutil.rmtree(result_dir) ``` ```python elif cargs.id: d = entry_dir(cargs.id) if d.is_dir(): shutil.rmtree(d) ``` ### Technical Analysis The `entry_dir()` function directly joins an unvalidated cache identifier to the configured cache root. It does not require the identifier to be a 12-character hexadecimal digest, reject absolute paths, reject `..` components, or verify that the resolved path remains under the cache root. The `cache clean --id` command passes its user-controlled identifier to this function and then recursively deletes the resulting directory with `shutil.rmtree()`. A second instance occurs in `materialize_tables()`. The user-controlled `--out` value becomes `result_name`, which is joined to the cache entry directory without validation. When `--force` is supplied, the resulting path is recursively deleted if it exists. `pathlib` does not provide containment enforcement when using `/`. A value containing traversal components can resolve outside the intended cache. An absolute right-hand operand can also replace the preceding path entirely. ### Attack Path #### Cache deletion path 1. An attacker influences the argument passed to `cache clean --id`. 2. The attacker supplies an escaping identifier, such as `../../target-directory`, or an absolute directory path. 3. `entry_dir()` constructs the path without vali ...[truncated 1315 chars]
Remediation
View remediation
str: if not CACHE_ID_RE.fullmatch(value): raise SystemExit("Invalid cache ID") return value ``` 2. Restrict result names to safe basenames: ```python RESULT_NAME_RE = re.compile(r"^result-[A-Za-z0-9_.-]+$") def validate_result_name(value: str) -> str: if not RESULT_NAME_RE.fullmatch(value): raise SystemExit("Invalid result name") return value ``` 3. Enforce path containment after canonicalization: ```python def contained_path(root: Path, child: str) -> Path: resolved_root = root.expanduser().resolve() candidate = (resolved_root / child).resolve() try: candidate.relative_to(resolved_root) except ValueError: raise SystemExit("Path escapes the permitted root") return candidate ``` 4. Apply the containment check immediately before every `shutil.rmtree()` operation, rather than relying only on earlier validation. 5. Reject absolute paths, path separators, empty identifiers, `.` components, and `..` components. 6. Add regression tests covering absolute paths, nested traversal, symbolic links, and traversal supplied through both `--id` and `--out`. 7. Consider deleting only known cache layouts containing an expected marker file, while still retaining canonical containment checks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wm_report.py:5203
Finding

Unrestricted PDF URL Fetching Permits SSRF and Network-Policy Bypass

Content
View full analysis
None: attempts = [ {"headers": {"User-Agent": UA}, "no_proxy": True}, {"headers": {"User-Agent": UA, "Referer": "https://www.cninfo.com.cn/"}, "no_proxy": True}, {"headers": {"User-Agent": UA}, "no_proxy": False}, ] last_err = None for a in attempts: try: with _open_url(url, a["headers"], no_proxy=a["no_proxy"]) as resp, dest.open("wb") as f: shutil.copyfileobj(resp, f) return except Exception as e: last_err = e raise SystemExit(f"Download failed: {url}; final error: {last_err}") ``` ```python ap.add_argument("--pdf-url", default="") ... if args.pdf_url: item = { "pdf_url": args.pdf_url, "title": args.title or args.pdf_url.split("/")[-1], } ... pdf_url = item.get("pdf_url") ... download_pdf(pdf_url, tmp) ``` ### Technical Analysis The Skill legitimately needs network access to download user-selected reports. However, `--pdf-url` is passed to `urllib` without enforcing a protocol allowlist or destination policy. The implementation does not: - Restrict requests to HTTP or HTTPS - Reject loopback, private, link-local, reserved, or multicast addresses - Block cloud instance metadata destinations - Reject URL-embedded credentials - Revalidate the destination after redirects - ...[truncated 2328 chars]
Remediation
View remediation
MAX_PDF_BYTES: raise SystemExit("PDF exceeds the permitted size") f.write(chunk) ``` 8. Require a PDF-compatible content type where available and verify that the downloaded file begins with the PDF signature before opening it. 9. Delete partial temporary files on all failure paths by using a `try/finally` block. 10. Respect system proxy policy by default. If direct access is required for a controlled deployment, expose it as an explicit opt-in option rather than silently attempting it first. 11. Add tests for loopback addresses, IPv6 loopback, private networks, encoded IP forms, redirects to blocked destinations, DNS changes, oversized responses, and unsupported URL schemes. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:18
Finding

Unpinned Third-Party Dependencies Prevent Reproducible and Verifiable Installation

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

声明描述的是一个面向财报 PDF 的端到端抽取与审计系统,而实际代码只是 industry.py 中的声明式行业关键词目录。它提供 INDUSTRY_HINTS、TITLE_INDUSTRY_HINTS、TRANSPORT_SEGMENT_HINTS 等静态映射,用于行业识别或路由辅助。这与所宣称的核心能力——PDF 获取与解析、表格抽取、可提取性 meta、adapt-plan、records 预提取、materialize 分表、勾稽校验、quote 回验、review-extract 审核——都不一致。虽然行业识别可能在更大系统中作为辅助组件存在,但就该代码块本身而言,其主要用途与声明的主要用途明显不符,因此应判定为 mismatch。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is about end-to-end extraction from financial-report PDFs: converting PDFs, extracting tables and facts, attaching page/quote provenance, enforcing quality/review outputs, and optionally fetching PDFs directly. The supplied code does none of that. It neither parses PDFs nor extracts report values, tables, quotes, pages, or review artifacts. Instead, it contains domain policy logic for resolving conflicts among industry labels by modifying industry scores using keyword evidence from title/body text. That is a materially different primary purpose, not merely a supporting implementation detail of the declared extraction pipeline. Therefore the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill explicitly supports network fetching, file reads/writes, and environment-dependent interpreter selection, but it declares no tool/permission scope. That creates an over-privileged and under-specified execution model where a host agent may allow broader capabilities than users or reviewers expect, increasing the risk of unintended data access, arbitrary file modification, or untrusted remote PDF retrieval.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The natural-language reason fields are written only in Chinese, which imposes a specific language in the skill data without any visible user opt-in or explanation that the file is intended for a Chinese-only workflow. This can violate organizational language/locale policy when the skill is expected to be generally usable.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The JSON content specifies extraction fields entirely in Chinese and references a Chinese-language report, which implies the skill input is constrained to a specific language/locale. There is no accompanying indication that this is optional, user-selected, or limited to a documented region-specific use case.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file contains user-facing natural-language instructions exclusively in Chinese, and there is no indication that the skill is region-specific or that users may opt into another language. Under the language/locale policy rule, forcing a specific language without opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The document title and all operative instructions are written in Chinese, which imposes a specific language for skill usage. There is no indication that users may choose another language or that the locale restriction is explicitly justified as a region-specific requirement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The document title and all operational instructions are written exclusively in Chinese, and there is no indication that language choice is optional or user-selected. Under the policy rule for language/locale, a skill that effectively forces a specific language without opt-in can violate organizational language choice requirements.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code includes natural-language comments and domain terminology written in Chinese, and similar Chinese-specific literals continue throughout the file. For a general-purpose skill, embedding a single required language/locale without user opt-in or an explicit documented justification can violate language/locale policy expectations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This file defines regex signatures and tokens almost entirely around Chinese section titles and terminology, indicating the skill is effectively constrained to a specific language/locale. There is no visible natural-language disclosure in this file that the skill is Chinese-only, offers language choice, or justifies the locale restriction as a documented regional specialization.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The function writes a self-contained report.html containing payload data assembled from manifest, review, gaps, findings, and row-level source quotes/pages. This is a file write of potentially sensitive document content, but the function has no confirmation prompt, logging/print statement, or inline warning describing that extracted source text will be persisted to disk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The generated HTML sets lang="zh-CN" and all visible UI strings are fixed in Chinese, which forces a specific language/locale experience. The file does not offer opt-in/selection for other locales or explain that the skill is intentionally limited to a China-specific workflow.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The module docstring and all user-facing CLI descriptions/messages in this file are written in Chinese, with no indication that users may opt into another language. Under the policy, forcing a specific language without user opt-in is a natural-language locale violation unless it is clearly documented as a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

Lines L10-L11 describe support for "繁体中期报告" / "traditional-Chinese interims" as a built-in capability, but there is no indication here that language or locale handling is user-selectable or opt-in. Because SQP-3 covers natural-language locale policy concerns in all file types, this is a mild policy finding.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The JSON includes Chinese-only reason strings, which can indicate a language-specific constraint in the skill's natural-language content without offering user opt-in or documenting why that locale is required. Under the stated policy, language or locale restrictions should be optional or clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The title and description are primarily written in Chinese, with no indication that users may choose another language or that the skill is intentionally limited to Chinese-speaking users. This can create a locale-policy concern because the skill presentation implicitly favors a specific language without documenting opt-in or scope constraints.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This Python file contains numerous natural-language string literals for labels, titles, and descriptions written in Chinese, but there is no indication in the file that the skill offers a language or locale choice. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation, even when embedded in code constants.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

This file's natural-language content is overwhelmingly Chinese-specific, including all keyword catalogs and explanatory comments, with no indication that the skill offers a language choice or that the locale restriction is explicitly justified. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The docstring text explicitly states behavior and usage in Chinese only, which constitutes a natural-language locale constraint. There is no indication that users may choose another language or that the Chinese-only wording is required for a documented region-specific purpose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The module docstring is written entirely in Chinese: "纯函数单测(不依赖 docling/pymupdf)". For a code file, embedded natural-language instructions or descriptions that assume a single language can violate locale policy when no user choice or justification is provided.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This code performs outbound HTTP(S) requests to arbitrary PDF URLs and to filing endpoints, which is a safety-relevant network operation. While there is a progress print for the download, there is no clear user disclosure in this file that remote requests will be made and that the provided URL or symbol/report parameters will be transmitted to external services.

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/wm_report.py (reported line 5313)May include surrounding context.

python
# 关掉的开关不能传空串占位——argparse 会把 "" 当未知位置参数报错
    argv = [sha, "--threads", str(args.threads), "--device", args.device]
    for flag in ("accurate", "ocr", "force"):
        if getattr(args, flag, False):
            argv.append(f"--{flag}")
    return argv