Back to skill

Security audit

Pdf To Image Preview

Security checks for vulnerabilities and agentic risk

Overview

This is mostly a normal PDF-to-image converter, but its ZIP option and unbounded rendering settings create real local file exposure and resource-exhaustion risks.

Use this only with PDFs and output directories you trust. Prefer a fresh private output directory, avoid the ZIP option in shared or untrusted directories, and keep DPI values conservative. The publisher should restrict ZIP contents to files generated in the current run, reject symlinks, add resource limits, and fix the outdated HTML-preview documentation.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/convert_pdf_to_images.py:98
Finding

Symbolic Link Following Allows Unauthorized Local File Inclusion in ZIP Archives

Content
View full analysis

Vulnerability Details

File Location: scripts/convert_pdf_to_images.py, lines 98–116
Vulnerability Type: Symbolic-link-based local file disclosure
Risk Level: Medium

Vulnerable Code

python
# Get all image files
image_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp')
image_files = sorted(
    [f for f in os.listdir(images_dir) if f.lower().endswith(image_extensions)]
)

if not image_files:
    print(f"Error: no image files found in directory {images_dir}")
    sys.exit(1)

# Create ZIP file
try:
    with zipfile.ZipFile(zip_output, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for image_file in image_files:
            image_path = os.path.join(images_dir, image_file)
            # Add to ZIP, preserving the filename
            zipf.write(image_path, image_file)
            print(f"  Added: {image_file}")

Technical Analysis

The ZIP-generation function enumerates every directory entry whose name ends with a supported image extension and passes its path directly to zipfile.ZipFile.write(). It does not verify that an entry is a regular file, reject symbolic links, or ensure that the resolved path remains within the intended image directory.

When an entry such as secret.jpg is a symbolic link to another readable local file, zipf.write() follows the link and archives the target file's contents under the harmless-looking name secret.jpg. The function also archives pre-existing image-named entries rather than restricting the archive to files generated during the current conversion.

Exploitation requires the attacker to be able to create or influence entries in the selected output directory before ZIP creation.

Attack Path

  1. An attacker gains write access to, or otherwise controls, the output directory selected through --output-dir.
  2. The attacker creates an image-named symbolic link, for example:
    text
    images/secret.jpg -> /path/to/sensitive/f
    

...[truncated 992 chars]

Remediation
View remediation

Remediation Suggestions

  1. Archive only the exact paths returned by pdf_to_images() during the current invocation, rather than enumerating every image-named entry in the directory.
  2. Reject symbolic links explicitly with os.path.islink() or Path.is_symlink().
  3. Require each candidate to be a regular file.
  4. Resolve the output directory and candidate paths and verify that every candidate remains under the resolved output directory.
  5. Where supported, use descriptor-based operations and no-follow semantics to reduce time-of-check/time-of-use race exposure.
  6. Consider creating a new private output directory with restrictive permissions for each conversion.

Example validation logic:

python
from pathlib import Path

base_dir = Path(images_dir).resolve()

for image_file in generated_image_paths:
    candidate = base_dir / image_file

    if candidate.is_symlink() or not candidate.is_file():
        raise ValueError(f"Unsafe archive entry: {image_file}")

    resolved = candidate.resolve()
    if base_dir not in resolved.parents:
        raise ValueError(f"Archive entry escapes output directory: {image_file}")

    zipf.write(resolved, image_file)

The containment check and file opening should occur as closely together as possible. For stronger protection against concurrent replacement, use platform-appropriate no-follow file operations.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/convert_pdf_to_images.py:47
Finding

Unbounded PDF Rasterization Parameters Permit Resource Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/convert_pdf_to_images.py, lines 47–63 and 161–166
Vulnerability Type: Uncontrolled resource consumption
Risk Level: Medium

Vulnerable Code

python
print(f"Processing PDF file with {total_pages} pages...")

image_paths = []
zoom = dpi / 72.0  # Calculate scaling ratio

for page_num in range(total_pages):
    page = pdf_document.load_page(page_num)
    mat = fitz.Matrix(zoom, zoom)  # Scaling matrix
    pix = page.get_pixmap(matrix=mat)

    # Generate filename
    file_ext = "png" if image_format.lower() == "png" else "jpg"
    filename = f"page_{page_num + 1:03d}.{file_ext}"
    image_path = os.path.join(output_dir, filename)

    # Save image
    if file_ext == "jpg":
        pix.save(image_path, jpg_quality=95)
    else:
        pix.save(image_path)
python
parser.add_argument(
    '--dpi',
    type=int,
    default=200,
    help='Image resolution (DPI), default: 200'
)

Technical Analysis

The command-line parser accepts any integer for --dpi, and the value is used directly to construct the rasterization matrix. No minimum or maximum DPI is enforced. The implementation also does not validate PDF page dimensions, estimate the resulting pixel count, limit input size, constrain cumulative output size, or enforce memory and execution-time budgets.

Raster memory grows approximately with the rendered width multiplied by height and channel count. Because both dimensions scale with DPI, memory consumption grows approximately quadratically as DPI increases. A PDF containing unusually large page dimensions can produce the same effect even when the default DPI is used.

The existing MAX_PAGES = 100 restriction limits only the number of pages. It does not prevent a single page from requiring an excessive bitmap allocation or prevent generated images from exhausting available disk space.

Attack Path

  1. An a ...[truncated 1204 chars]
Remediation
View remediation

Remediation Suggestions

  1. Enforce a conservative DPI range during argument parsing, such as a documented minimum and maximum appropriate to the deployment.
  2. Reject zero, negative, and excessively large DPI values.
  3. Before rasterization, calculate the expected output width, height, and total pixel count from the page rectangle and DPI.
  4. Reject pages whose estimated pixel count or bitmap memory exceeds a configured threshold.
  5. Set limits for input PDF size, cumulative rendered pixels, output bytes, processing time, and available disk space.
  6. Process untrusted PDFs in an isolated worker with operating-system memory, CPU, file-size, and execution-time limits.
  7. Handle allocation and rendering failures cleanly, close the PDF in a finally block or context manager, and remove incomplete output files.
  8. Consider lower deployment-specific limits than the documented maximum when running in shared or memory-constrained environments.

Example argument validation:

python
MIN_DPI = 36
MAX_DPI = 600
MAX_PIXELS_PER_PAGE = 100_000_000

if not MIN_DPI <= dpi <= MAX_DPI:
    raise ValueError(
        f"DPI must be between {MIN_DPI} and {MAX_DPI}"
    )

page_rect = page.rect
width_px = int(page_rect.width * dpi / 72.0)
height_px = int(page_rect.height * dpi / 72.0)
pixel_count = width_px * height_px

if width_px <= 0 or height_px <= 0:
    raise ValueError("Invalid rendered page dimensions")

if pixel_count > MAX_PIXELS_PER_PAGE:
    raise ValueError("Rendered page exceeds the configured pixel limit")

The thresholds should be selected from measured memory requirements and the runtime's actual resource budget rather than treated as universal values.

Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • 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 (8)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares executable behavior that reads user-supplied local PDF files, but it does not define any explicit tool scope such as permissions or allowed-tools. In an agent environment, missing scope boundaries can let the skill be invoked with broader file access than intended, increasing the chance of unintended local file exposure or overreach.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger conditions are broad ('convert PDF to image', 'extract PDF pages', 'image-ify PDF content') and do not clearly constrain when the skill should activate. In an automated agent setting, vague activation rules can cause the skill to run on unintended documents or contexts, leading to unnecessary file access or surprising actions on local content.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest description says the skill converts each PDF page into image files with configurable format and resolution, but this documentation presents automatic HTML preview generation as a core feature. Generating an HTML file is a distinct output behavior not reflected in the manifest’s stated scope.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The description is written only in Chinese, which indicates a fixed language presentation without any user opt-in or alternative locale guidance. Under the policy, language constraints should either be optional, explicitly user-selected, or clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill documentation appears to force a specific language/locale by presenting all instructions in Chinese with no indication of user choice, alternative locale, or justification for a China-specific audience. The policy calls for flagging language or locale constraints when they are imposed without opt-in or documented regional scope.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file demonstrates running the conversion command and later documents the output directory and HTML file, but it does not explicitly warn users that the skill will create multiple image files and a preview HTML file on disk. For markdown files, omissions of warnings about behaviors that affect user data or system state should be flagged when the documentation does not disclose them clearly.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script's docstrings, help text, status messages, and errors are written in Chinese, beginning with the title and continuing through user-facing output. For a general-purpose utility, this imposes a specific language on users without opt-in or a documented region-specific justification, which matches the locale-policy concern.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes converting each PDF page into image files with configurable format and resolution. In addition to that stated behavior, the script exposes a separate ZIP-packaging capability via create_zip() and the --zip/--zip-output CLI options, which is not mentioned in the manifest description.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.