T09 · Insecure Skill Coding Practices
- Location
scripts/pdf_text_replace.py:541- Finding
Predictable Shared Temporary Paths Expose Decrypted PDF Content
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a real PDF text-editing tool, but it needs review because encrypted PDFs, passwords, and document excerpts can be left exposed on disk or in process arguments.
Install only if you are comfortable using it on local PDFs with sensitive-document precautions. Avoid processing confidential or encrypted PDFs unless you can control temporary files, clean up /tmp afterward, and accept that password-protected documents may be written out decrypted unless explicitly re-encrypted.
scripts/pdf_text_replace.py:541Predictable Shared Temporary Paths Expose Decrypted PDF Content
scripts/pdf_text_replace_v2_advanced.py:568PDF Password Exposed Through qpdf Process Arguments
scripts/pdf_text_replace_v2_advanced.py:568Successful qpdf Decryption Leaves a Plaintext Temporary PDF Behind
SKILL.md:13Third-Party Dependencies Are Installed Without Version or Integrity Pinning
The declared description promises a PDF text-replacement tool with broad capabilities across many PDF types and editing scenarios. The supplied code chunk instead only creates a demo/sample PDF certificate for testing, with carefully placed text and embedded fonts to facilitate another tool's operation. Its primary purpose is PDF generation, not PDF modification. There is no logic for reading PDFs, locating target text, replacing content, preserving original layout during replacement, decrypting PDFs, performing OCR, or handling arbitrary user-requested changes. This is a clear description-behavior mismatch.
The description is broader than the supplied code. The code genuinely supports some declared areas: PDF text replacement, custom font/CMap decoding, variable-length replacements to a limited extent, and encrypted PDF handling with multiple fallback strategies. However, several prominent claims are not represented in the code chunk. There is no OCR pipeline or image processing for scanned PDFs, no logic for modifying styles, and no comprehensive handling proving support for 'any PDF.' Replacement is limited to parsed Tj/TJ stream operations and depends on available font mappings; many PDFs use other representations or more complex layouts. Therefore the declared description materially overstates the actual behavior.
The code substantially matches part of the description: it does support variable-length replacement and style changes, and it includes limited embedded font subset creation when replacement characters are missing. However, the declared purpose is broader than the actual implementation. There is no code for decrypting/opening encrypted PDFs, no OCR or image-analysis pipeline for scanned PDFs, and no end-to-end handling of arbitrary PDFs. Instead, the module exposes helper functions that manipulate already-decoded PDF text streams and depend on caller-supplied font mappings. Therefore the description overstates the capability and universality of the implementation.
The code does perform PDF text replacement, but only through an OCR-and-raster workflow aimed at scanned/image-based PDFs. It does not implement native text-object editing for standard vector PDFs, nor the advanced capabilities claimed in the description such as custom font encoding handling, embedded subset decoding, robust encrypted-PDF handling, high-fidelity preservation, or generalized style/layout-aware replacement. The description overstates breadth and fidelity: this script is a specialized OCR replacement tool, not a comprehensive 'replace text in any PDF' solution.
The code does perform PDF text replacement, so it is related to the declared purpose, but the description substantially overstates its scope and robustness. The implementation is a specialized stream-level replacer for Tj/TJ operations on a selected page. It parses Tm/Tf and text drawing operators, optionally decodes bytes using ToUnicode CMaps, and substitutes text if it can encode the replacement back into the font mapping or latin-1. There is no OCR pipeline, no image processing, no encrypted PDF logic, no general style editing, and no evidence of comprehensive handling for arbitrary custom encodings beyond simple ToUnicode extraction. It also may reduce multiple operations to a single Tj, which does not guarantee preserved visual fidelity. Therefore the description is materially broader than the actual behavior.
The declared description promises a comprehensive PDF text replacement tool that can modify specific text in essentially any PDF, including difficult cases such as custom encodings, embedded subsets, encrypted files, scanned/OCR PDFs, and variable-length replacements. This code chunk is much narrower: it is a style-overlay helper that finds existing visible text on one page, whites it out, and redraws the same text with optional styling changes. Its primary purpose is styling, not content replacement. It lacks any logic for OCR, font-encoding recovery, replacement with different text, handling variable-length substitutions, or broad encrypted-PDF workflows. Therefore the code materially under-delivers relative to the declared purpose, so this is a mismatch.
The declared description promises a broad, high-fidelity PDF text replacement capability across many PDF types and complexities, including encoded text, embedded subset fonts, encrypted PDFs, OCR scans, variable-length replacements, and style changes. The supplied code chunk is much narrower: it is explicitly an OCR module for image-based PDFs. It detects whether a page seems image-based, rasterizes the page, OCRs visible text, removes matched regions by inpainting, redraws replacement text with PIL, and replaces the PDF page content with the resulting image. This supports one slice of the declared functionality—OCR-based replacement for scanned/image pages—but does not implement the broader PDF text editing features claimed. In particular, there is no code for parsing/editing native PDF text objects, handling custom encodings or embedded subset fonts, decrypting encrypted PDFs, preserving original typography/styles, or robustly accommodating variable-length replacements. Because the actual behavior is materially narrower and different from the declared all-purpose PDF text replacement description, this is a mismatch.
The skill declares capabilities that imply reading, writing, and invoking shell commands, but it does not define any explicit tool scope or permission boundaries. In an agent environment, that increases the chance of over-broad filesystem access or unintended command execution against user-provided PDF paths and related resources.
The skill explicitly encourages modification of certificates, contracts, and encrypted PDFs, including password handling, without a clear warning about legal sensitivity, confidentiality, or secret exposure. In an agent setting, that increases the chance users paste passwords into prompts or use the tool on sensitive documents without understanding the privacy and integrity consequences.
The code writes a cropped verification image derived from the processed PDF to a fixed path in /tmp, which can expose potentially sensitive document content on disk without the caller’s explicit consent. Using a predictable filename also increases the chance of unintended disclosure, overwrites, or symlink-based file redirection in shared environments.
During qpdf fallback, the code decrypts the PDF into a temporary file on disk and explicitly leaves cleanup to the caller. This can expose sensitive document contents in plaintext to other local users, backups, forensic recovery, or later accidental disclosure, especially because the function is designed to handle encrypted PDFs where confidentiality is expected.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if password:
cmd += [f"--password={password}"]
cmd += [path, tmp_path]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
reader = PdfReader(tmp_path)
print(f"[open_pdf_smart] Decrypted with qpdf subprocess -> temp file '{tmp_path}'")
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return p
# Try PATH
try:
result = subprocess.run(["which", "qpdf"], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
The save path defaults to re_encrypt=False, and the implementation states that output will be unencrypted/decrypted in that case. In a skill whose purpose includes opening encrypted PDFs transparently, this creates a meaningful confidentiality risk: users may modify a protected document and unknowingly write a plaintext version to disk, defeating the original protection model.
The docstring for replace_page_image says it will 'Replace a page's image content in the PDF,' which implies preserving the rest of the PDF. However, the reportlab fallback path delegates to logic that, when pypdf is unavailable, simply copies a single-page overlay PDF to the output, losing all other pages. This is an active contradiction between the documented behavior and actual behavior in that execution path.
The inline comments state 'Clear existing content and insert new image' and 'Use insert_image which replaces the entire page with the image.' In reality, page.clean_contents() normalizes content streams but does not guarantee complete removal of all existing page objects, and insert_image adds an image to the page rather than semantically replacing the page contents. The comments overstate what the code guarantees.
This Python file contains natural-language documentation and user-facing print strings entirely in Chinese, and the interface does not provide any language or locale opt-in. Under the policy, forcing a specific language without user choice is a natural-language policy violation unless the locale restriction is clearly justified.
The function saves changes directly back to the original PDF path using incremental in-place update semantics, with no backup creation, alternate output path, or confirmation mechanism in this code path. In a text-replacement skill, this can cause irreversible corruption or unintended modification of user documents if the wrong file, page, or match set is targeted, especially when processing encrypted or complex PDFs.
The primary title and descriptive content are written in Chinese, and the file does not indicate that other languages are supported or that the user may choose their preferred language. This is a natural-language policy concern because it imposes a specific language without opt-in or documented justification.
This code performs a file write to the caller-supplied output path, which can overwrite or create a PDF on disk. Although the function docstring says the result is written to output_path, there is no runtime confirmation prompt or explicit user-facing warning at the point of the write.
The code hard-codes a default OCR language setting of "chi_sim+eng", which imposes a specific language/locale behavior on users unless they explicitly override it. This is a natural-language policy concern because the skill chooses language handling by default rather than prompting for or documenting a user preference.
The main replacement pipeline exposes a default language parameter of "chi_sim+eng", which means the skill's primary behavior assumes a specific language set without explicit user choice. Because this is part of the top-level API, the locale preference is embedded in normal operation rather than being opt-in.
No suspicious patterns detected.