Back to skill

Security audit

pdf-editing

Security checks for vulnerabilities and agentic risk

Overview

This PDF editing skill is a coherent local PDF-editing guide, but users must not rely on its visual text replacement as redaction.

Install only if you understand that ordinary replacement in this skill may hide old PDF text visually without removing it. For names, emails, dates of birth, IDs, financial details, or anything confidential, use the true redaction workflow and verify the original value is not extractable from the saved PDF.

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

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:134
Finding

Visual Text Replacement Leaves Original PDF Content Recoverable

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 21–22, 134–164, and 247–261
Vulnerability Type: Sensitive information retained beneath a visual overlay
Risk Level: Medium

Vulnerable Code

python
# To replace a value you found that needs changing:
old_value = "..."  # The value you found in the PDF that's wrong
new_value = "..."  # The correct value from your input source

rects = page.search_for(old_value)
if rects:
    rect = rects[0]

    # STEP 1: Draw WHITE rectangle to COMPLETELY COVER old text
    page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)

    # STEP 2: Insert new text at the SAME position (not offset!)
    page.insert_text((rect.x0, rect.y1), new_value, fontsize=11, color=(0, 0, 0))

doc.save("output.pdf")

The same unsafe replacement method is repeated in the comparison workflow:

python
for old_val, new_val in replacements.items():
    if old_val == new_val:
        continue  # Skip if already correct
    rects = page.search_for(old_val)
    if rects:
        rect = rects[0]
        # Cover old text with white rectangle
        page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)
        # Insert new text
        page.insert_text((rect.x0, rect.y1), new_val, fontsize=11, color=(0, 0, 0))

doc.save("output.pdf")

Technical Analysis

The recommended replacement process draws an opaque white rectangle over the original value and inserts new text above it. This changes only the rendered appearance; it does not remove the original text object from the PDF content stream. The Skill itself recognizes this distinction elsewhere by stating that draw_rect() is a visual cover and that underlying text remains machine-extractable.

Consequently, an output PDF can visibly display the replacement while retaining both the old and new values in its internal text layer. The hidden value may be recovered through text extraction, ...[truncated 1917 chars]

Remediation
View remediation

Remediation Suggestions

Replace the visual-cover workflow with true redaction for all existing-text replacements:

  1. Locate the exact text rectangle.
  2. Add a redaction annotation with an appropriate fill.
  3. Apply the redaction so the original text object is removed from the PDF structure.
  4. Insert the replacement value at the original position.
  5. Save to a new output file with garbage collection and stream cleanup enabled where supported.
  6. Reopen the saved output and verify that the original value is absent from extracted text.

A safer replacement pattern is:

python
rects = page.search_for(old_value)
if rects:
    rect = rects[0]
    page.add_redact_annot(rect, fill=(1, 1, 1))
    page.apply_redactions()
    page.insert_text(
        (rect.x0, rect.y1),
        new_value,
        fontsize=11,
        color=(0, 0, 0),
    )

doc.save(
    "output.pdf",
    garbage=4,
    clean=True,
    deflate=True,
)

Post-save verification should fail closed:

python
with fitz.open("output.pdf") as check_doc:
    extracted = "\n".join(page.get_text() for page in check_doc)
    if old_value in extracted:
        raise RuntimeError("Replacement failed: original value remains extractable")

The documentation should no longer present draw_rect() as the default method for replacing existing text. If visual covering must remain available for a specialized preservation use case, clearly label it as non-redacting, warn that the original value remains recoverable, and require explicit user confirmation before using it.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep

Static analysis

No suspicious patterns detected.