T09 · Insecure Skill Coding Practices
- 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: MediumVulnerable 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:
- Locate the exact text rectangle.
- Add a redaction annotation with an appropriate fill.
- Apply the redaction so the original text object is removed from the PDF structure.
- Insert the replacement value at the original position.
- Save to a new output file with garbage collection and stream cleanup enabled where supported.
- 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.
