Back to skill

Security audit

Merge Reimbursement PDFs

Security checks for vulnerabilities and agentic risk

Overview

This skill does the promised local PDF merging, but it can automatically install Python packages and LibreOffice on the host by default, which deserves user review before installation.

Review this skill before installing. Use it only in a controlled environment or virtual environment, prefer preinstalling dependencies yourself, run with --no-auto-install or MERGE_TRAVEL_PDFS_NO_AUTO_INSTALL=1, and point --render-check at a dedicated empty directory because matching page-*.png files there may be deleted.

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

T08 · Insecure Dependencies

Warning
Location
scripts/merge_travel_pdfs.py:25
Finding

Automatic Installation of Unpinned Third-Party Dependencies

Content
View full analysis
None: if not AUTO_INSTALL_DEPS: raise RuntimeError("LibreOffice/soffice not found and auto-install is disabled") system = platform.system() commands: list[list[str]] = [] if system == "Darwin": brew = shutil.which("brew") if brew: commands.append([brew, "install", "--cask", "libreoffice"]) elif system == "Windows": winget = shutil.which("winget") choco = shutil.which("choco") if winget: commands.append([winget, "install", "--id", "TheDocumentFoundation.LibreOffice", "-e", "--accept-package-agreements", "--accept-source-agreements"]) if choco: commands.append([choco, "install", "libreoffice-fresh", "-y"]) else: apt = shutil.which("apt-get") dnf = shutil.which("dnf") yum = shutil.which("yum") if apt: commands.append([apt, "update"]) commands.append([apt, "install", "-y", "libreoffice"]) if dnf: commands.append([dnf, "install", "-y", "libreoffice"]) if yum: commands ...[truncated 3241 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/merge_travel_pdfs.py:510
Finding

Destructive Cleanup of Arbitrary User-Selected Render Directory

Content
View full analysis
list[str]: check_dir.mkdir(parents=True, exist_ok=True) for old_png in check_dir.glob("page-*.png"): old_png.unlink() rendered: list[str] = [] ``` The directory can be supplied directly by the caller: ```python parser.add_argument("--render-check", type=Path, help="Directory for rendered check PNGs") ``` ```python render_check_dir = args.render_check.expanduser().resolve() if args.render_check else default_render_check_for(root).resolve() ``` ### Technical Analysis The `--render-check` argument permits the caller to select an arbitrary existing directory. Before generating preview images, `render_check()` deletes every file in that directory whose name matches `page-*.png`. The deletion logic does not verify that matching files were generated by this application, does not maintain a manifest of prior outputs, and does not require the directory to be empty or dedicated to this operation. Consequently, unrelated files with matching names can be deleted. The use of `Path.glob()` limits deletion to direct children matching the pattern, and no recursive deletion is performed. Nevertheless, deletion occurs with the full filesystem permissions of the invoking user and is irreversible through the application. ### Attack Path 1. A user, wrapper script, or automation system supplies an existing shared directory through `--render-check`. 2. That directory already contains unrelated files such as `page-001.png` or `page-reference.png`. 3. The merge reaches the render-verification stage. 4. `check_dir.glob("page-*.png")` selects those unrelated files. 5. `unlink()` permanently removes each selected file before new previews are generated. An attacker able to influence comman ...[truncated 656 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared purpose is document merging, but the skill also performs package installation, OS-level software installation, rendering, reporting, classification, and subfolder batch processing. This mismatch is dangerous because users may approve a seemingly simple file-processing skill without realizing it can alter the host system, invoke package managers, and process a much broader file scope than expected.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill is a local PDF-merging utility, but it automatically installs Python packages via pip when imports fail. That exceeds user expectations for a file-processing script and creates unnecessary supply-chain and environment-modification risk on the host system.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code can automatically invoke OS package managers to install LibreOffice if soffice is unavailable. In the context of a document-merging skill, this is disproportionately powerful behavior that modifies the host system and broadens exposure to repository compromise, privilege misuse, and unintended software changes.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares no explicit tool scope while the documented behavior requires shell execution, environment access, and file writes. In an agent setting, this creates an over-privileged and under-specified execution model where the agent may invoke powerful capabilities without clear user-visible boundaries, increasing the chance of unsafe file or system modifications.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file includes a dedicated Chinese instruction section and repeatedly uses Chinese-specific document terminology, but it does not state that language choice is optional or user-selected. Under the policy, forcing a specific language or locale without opt-in is a natural-language policy violation unless clearly justified.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The documentation explicitly allows automatic installation of Python packages and LibreOffice through system package managers such as brew, winget, choco, apt, dnf, or yum. Allowing a document-processing skill to modify the system and fetch software from external sources materially expands the attack surface and can lead to unintended privileged changes, supply-chain exposure, or execution in environments where such modifications are prohibited.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

The script automatically runs pip install at import time when dependencies are missing, causing network access and code installation merely by invoking the tool. This is dangerous because it introduces supply-chain risk, unexpected environment mutation, and possible execution of package installation hooks without explicit user approval.

Content

Scanner excerpt · scripts/merge_travel_pdfs.py (reported line 35)May include surrounding context.

python
if not AUTO_INSTALL_DEPS:
            raise
        print(f"Installing missing Python package: {package_name}", file=sys.stderr)
        subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
        return importlib.import_module(module_name)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
98% confidence
Finding

The script automatically executes system package-manager commands such as apt, dnf, yum, brew, winget, or choco on the host. This goes beyond document merging and performs privileged system modification, which can install large software packages, alter system state, and create supply-chain exposure if repositories or package sources are compromised.

Content

Scanner excerpt · scripts/merge_travel_pdfs.py (reported line 390)May include surrounding context.

python
print("LibreOffice/soffice not found. Attempting automatic install...", file=sys.stderr)
    for command in commands:
        result = subprocess.run(command, check=False)
        if result.returncode != 0:
            raise RuntimeError(f"automatic LibreOffice install failed while running: {' '.join(command)}")

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 · scripts/merge_travel_pdfs.py (reported line 411)May include surrounding context.

python
source = prepare_spreadsheet_for_pdf(path, temp_dir)
    before = {p.resolve() for p in temp_dir.glob("*.pdf")}
    result = subprocess.run(
        [soffice, "--headless", "--convert-to", "pdf", "--outdir", str(temp_dir), str(source)],
        check=False,
        text=True,

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
96% confidence
Finding

The dependency is specified with only a lower bound, so builds may resolve to different versions over time and can silently pull in vulnerable or breaking releases. For a file-processing skill that handles PDFs, images, and spreadsheets, this weakens supply-chain control and makes it difficult to verify whether known security fixes are present.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
PyMuPDF>=1.23
Pillow>=10
openpyxl>=3.1

Unverifiable Dependency: PyMuPDF has 2 known advisory(ies) (CVE-2026-3029 (PyMuPDF has a path traversal in _main_.py); CVE-2026-3029 (PyMuPDF has a path traversal in _main_.py)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding

PyMuPDF has known advisories, and because the manifest does not pin a version, there is no way to confirm that deployment will avoid affected releases. In a skill centered on PDF handling, a vulnerable parser or CLI-adjacent component is especially relevant because untrusted document input is core to the feature set.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

Pillow is unpinned and may resolve to any future version satisfying the minimum constraint, which creates non-reproducible environments and can expose the skill to image-parsing vulnerabilities present in some releases. Because this skill explicitly processes screenshots and images from potentially untrusted reimbursement folders, dependency drift is more security-relevant than in a non-parser utility.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
PyMuPDF>=1.23
Pillow>=10
openpyxl>=3.1

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding

Pillow has multiple historical advisories, including memory corruption and resource-consumption issues, and the unpinned requirement prevents verifying whether a safe version will actually be installed. This matters more here because the skill processes phone screenshots and other images that may come from untrusted sources, increasing parser attack surface.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

openpyxl is also unpinned, so installs are not reproducible and may include versions with known parser-related issues or future regressions. Since the skill converts Excel workbooks to PDF, it likely opens user-supplied spreadsheet content, which raises the importance of using a known-safe version.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
PyMuPDF>=1.23
Pillow>=10
openpyxl>=3.1

Unverifiable Dependency: openpyxl has 2 known advisory(ies) (CVE-2017-5992 (Improper Restriction of XML External Entity Reference in Openpyxl); CVE-2017-5992 (Openpyxl 2.4.1 resolves external entities by default, which allows remote attack)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
86% confidence
Finding

openpyxl has known XML/entity-related security history, and without version pinning it is impossible to establish whether the installed release includes the necessary fixes. Given that the skill converts multi-sheet Excel workbooks, user-controlled spreadsheet files are likely to be parsed directly, which increases the practical risk of unsafe library versions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code embeds Chinese-only filenames and marker strings such as "合并结果", "合并报告", and "合并检查" as part of normal behavior. Because the script does not offer a user-selectable language or document a justified locale restriction, it can violate a language/locale policy requiring user choice or explicit justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The script generates default output/report/check paths with Chinese names while the rest of the CLI is in English, and there is no option to choose locale. That creates a forced locale behavior in user-visible outputs rather than offering language choice or clearly documenting the regional constraint.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.