Back to skill

Security audit

Redact

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real redaction toolkit, but it can leave or log unredacted sensitive text and may report a PowerPoint file as saved even when embedded-image redaction failed.

Install only after reviewing the privacy tradeoffs. Avoid using it on highly sensitive documents until temp-file cleanup, redaction-failure handling, and logging are fixed; run it in an isolated workspace and inspect outputs before sharing them.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/read.py:49
Finding

Predictable shared cache files expose sensitive document content and permit cross-process interference

Content
View full analysis

Vulnerability Details

File Location: scripts/read.py, lines 49-50, 109-111, 271-281, and 598
Vulnerability Type: Predictable temporary files and non-isolated shared workspace
Risk Level: High

Vulnerable Code

python
# Use a fixed temp directory to avoid repeated authorization prompts
_TEMP_DIR = Path.home() / ".cache" / "redact_temp"
_TEMP_DIR.mkdir(parents=True, exist_ok=True)
python
for idx, pred in enumerate(predictions):
    pred_dir = _TEMP_DIR / f"ppstructure_res_{idx}"
    pred_dir.mkdir(parents=True, exist_ok=True)
    pred.save_to_json(str(pred_dir))
python
def ocr_image(pipeline, image: Image.Image) -> Tuple[List[TextRegion], List[str]]:
    """Run OCR on a PIL Image and return text regions."""
    # Save image to temp file in fixed directory
    tmp_path = _TEMP_DIR / "ocr_temp_image.png"
    image.save(tmp_path, "PNG")

    try:
        results, markdown_texts = run_pipeline(pipeline, tmp_path)
        regions = collect_regions(results)
        return [r for r in regions if r.text], markdown_texts
    finally:
        if tmp_path.exists():
            tmp_path.unlink()
python
# Use fixed temp directory instead of creating new one
pdf_path = _TEMP_DIR / f"{file_path.stem}.pdf"

Technical Analysis

The reader places rendered images, OCR JSON, and converted PDFs in one persistent cache directory using predictable names. The names contain no process identifier or cryptographically random component. Every OCR operation uses ocr_temp_image.png, every first prediction uses ppstructure_res_0, and Office conversions use only the source filename stem.

Consequently, concurrent invocations can overwrite, consume, or delete one another's files. The application also does not explicitly enforce restrictive permissions on _TEMP_DIR or its files. Depending on the host's umask and cache-directory permissions, other local users may be ...[truncated 1630 chars]

Remediation
View remediation

Remediation Suggestions

  • Create a private per-invocation workspace with tempfile.TemporaryDirectory(prefix="redact-").
  • Explicitly set the directory mode to 0700 and generated-file modes to 0600.
  • Generate every image, PDF, and OCR result path inside that unique workspace.
  • Do not derive temporary names solely from user-controlled filename stems.
  • Reject symbolic links where files are security-sensitive, or use exclusive creation primitives such as os.open() with O_CREAT | O_EXCL | O_NOFOLLOW where supported.
  • Remove the entire workspace in a finally block using TemporaryDirectory cleanup.
  • Add concurrency tests that run multiple OCR and Office conversions simultaneously and verify that no files are shared.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/redact-image.py:95
Finding

OCR results containing unredacted sensitive text are persistently left in temporary directories

Content
View full analysis

Vulnerability Details

File Location: scripts/redact-image.py, lines 95-112; equivalent behavior occurs in scripts/redact-pdf.py lines 106-123, scripts/redact-document.py lines 361-367, and scripts/redact-presentation.py lines 490-496
Vulnerability Type: Plaintext sensitive-data retention in temporary storage
Risk Level: High

Vulnerable Code

python
def run_pipeline(pipeline: PPStructureV3, image_path: Path) -> List[Any]:
    """Run PPStructureV3 and export results as JSON."""
    temp_dir = Path(tempfile.mkdtemp(prefix="ppstructure_"))
    results: List[Any] = []
    
    eprint(f"Processing: {image_path}")
    predictions = pipeline.predict(input=str(image_path))
    
    for idx, pred in enumerate(predictions):
        pred_dir = temp_dir / f"res_{idx}"
        pred_dir.mkdir(parents=True, exist_ok=True)
        pred.save_to_json(str(pred_dir))
        
        for json_file in sorted(pred_dir.glob("*_res.json")):
            with json_file.open("r", encoding="utf-8") as f:
                results.append(json.load(f))
    
    return results

The document and presentation implementations similarly create directories but never remove them:

python
pred_dir = Path(tempfile.mkdtemp(prefix="ppstructure_"))
pred.save_to_json(str(pred_dir))

import json
for json_file in sorted(pred_dir.glob("*_res.json")):
    with json_file.open("r", encoding="utf-8") as f:
        data = json.load(f)
        regions.extend(extract_regions_from_json(data))

Technical Analysis

Each redaction workflow writes the raw PPStructure OCR output to a new directory under the system temporary location. Those JSON files contain text extracted before redaction and can therefore include names, identifiers, telephone numbers, addresses, and other information that the tool is explicitly being used to remove.

Neither the top-level directory nor its prediction subdirectories are ...[truncated 1783 chars]

Remediation
View remediation

Remediation Suggestions

  • Wrap OCR export in tempfile.TemporaryDirectory() so all JSON and subsidiary files are removed automatically.
  • Put cleanup in a finally block to cover parsing failures, OCR exceptions, and interrupted processing.
  • Avoid serializing OCR output entirely when the library provides an in-memory result API.
  • If serialization is unavoidable, enforce mode 0700 on directories and 0600 on files.
  • Add startup cleanup for abandoned directories from older versions, subject to ownership and path-validation checks.
  • Document that temporary storage may hold confidential information and allow callers to select an encrypted private workspace.
  • Add tests that assert no ppstructure_* directory remains after successful and failed operations.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/redact-image.py:626
Finding

Redaction logs disclose the exact sensitive values and surrounding OCR text

Content
View full analysis

Vulnerability Details

File Location: scripts/redact-image.py, lines 626-631; equivalent logging appears in scripts/redact-pdf.py lines 675-679 and 757-762, scripts/redact-document.py lines 129-134 and 396-400, and scripts/redact-presentation.py lines 116-121 and 525-529
Vulnerability Type: Sensitive information exposure through logs
Risk Level: Medium

Vulnerable Code

python
if rule.replacement is None:
    # Cover with solid color
    eprint(f"Covering: '{match.target}' in '{region.text}'")
    draw = ImageDraw.Draw(image)
    cover_sub_region(draw, sub_polygon)
else:
    # Replace text
    eprint(f"Replacing: '{match.target}' -> '{rule.replacement}' in '{region.text}'")
    image = replace_text_in_sub_region(
        image, region, sub_polygon, rule.replacement, font
    )

The native document and presentation paths also log rule values directly:

python
eprint(
    f"  Replaced '{rule.target}' -> "
    f"'{rule.replacement}' ({occurrences} times)"
)
python
redacted = REDACTION_CHAR * len(rule.target)
new_text = new_text.replace(rule.target, redacted)
eprint(
    f"  Redacted '{rule.target}' -> "
    f"'{redacted}' ({occurrences} times)"
)

Technical Analysis

Redaction targets are, by definition, likely to be sensitive. The scripts print the exact target, replacement, and—in OCR workflows—the full surrounding recognized region to standard error. Standard error is commonly captured by agent runtimes, CI/CD systems, shell history wrappers, centralized logging services, notebook systems, and job orchestration platforms.

This creates a secondary plaintext copy outside the redacted output. Logging the full OCR region can expose unrelated sensitive values that were not included in the target rule. Replacement values may also be confidential pseudonyms or tokens and are printed without masking.

Attack Path

  1. A user creates a rule co ...[truncated 973 chars]
Remediation
View remediation

Remediation Suggestions

  • Never log raw targets, replacements, OCR regions, document text, or filenames containing sensitive identifiers by default.
  • Log only aggregate information, such as page number and number of successful redactions.
  • If correlation is necessary, use a short keyed digest generated with a deployment-specific secret rather than an unsalted hash.
  • Add an explicit opt-in diagnostic mode and display a warning that it may expose document contents.
  • Configure production log collectors to redact known PII patterns and minimize retention.
  • Review existing logs and temporary agent transcripts for previously disclosed values, then remove them according to incident-response policy.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/redact-presentation.py:558
Finding

PowerPoint embedded-image redaction failures are suppressed while a successful output is still produced

Content
View full analysis

Vulnerability Details

File Location: scripts/redact-presentation.py, lines 558-594
Vulnerability Type: Fail-open privacy control and suppressed redaction failure
Risk Level: High

Vulnerable Code

python
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
    try:
        image = shape.image
        image_bytes = image.blob
        
        eprint(f"  Found image in slide {slide_idx}")
        pil_image = Image.open(io.BytesIO(image_bytes))
        
        redacted_image, img_count = redact_image(
            pil_image, rules, pipeline
        )
        
        if img_count > 0:
            output_buffer = io.BytesIO()
            redacted_image.save(output_buffer, format="PNG")
            output_buffer.seek(0)
            
            # Replace image in shape
            # Note: python-pptx doesn't support direct image replacement,
            # we need to use a workaround
            shape.image.blob = output_buffer.read()
            total_count += img_count
            eprint(
                f"  Redacted {img_count} text regions in image"
            )
        else:
            eprint("  No matching text found in image")
            
    except Exception as e:
        eprint(f"  Warning: Failed to process image: {e}")

# Save presentation
output_path.parent.mkdir(parents=True, exist_ok=True)
prs.save(str(output_path))

Technical Analysis

The code acknowledges that python-pptx does not support direct image replacement, but attempts to assign to shape.image.blob. If that operation is unsupported or any other image-processing step fails, the broad except Exception block only emits a warning.

Processing then continues, saves the presentation, prints an output path, and returns success. There is no failed-redaction count, output verification, or nonzero exit status. As a result, callers may reasonably treat the generated presentation as sanit ...[truncated 1334 chars]

Remediation
View remediation

Remediation Suggestions

  • Replace the underlying image part through a supported package-level API rather than assigning to shape.image.blob.
  • Verify the saved .pptx by reopening it and confirming that target values are absent from all embedded image blobs.
  • Treat any failure to process or replace an image as fatal by default and return a nonzero exit code.
  • Track attempted, successful, skipped, and failed redactions separately.
  • Do not print or return the output as successfully sanitized when any relevant element failed.
  • If partial output is required, mark it clearly as unsafe and require an explicit --allow-partial option.
  • Add integration tests containing PNG, JPEG, grouped, linked, duplicated, and unsupported image types.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

A PDF-focused implementation that also exports JSON and visualization artifacts can create additional copies of sensitive document contents while the skill is marketed as redaction-oriented. In a privacy workflow, undeclared analysis and export side effects are hazardous because they may preserve or reveal the very data the user intended to remove.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A PDF-focused implementation that also exports JSON and visualization artifacts can create additional copies of sensitive document contents while the skill is marketed as redaction-oriented. In a privacy workflow, undeclared analysis and export side effects are hazardous because they may preserve or reveal the very data the user intended to remove.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A PDF-focused implementation that also exports JSON and visualization artifacts can create additional copies of sensitive document contents while the skill is marketed as redaction-oriented. In a privacy workflow, undeclared analysis and export side effects are hazardous because they may preserve or reveal the very data the user intended to remove.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

A PDF-focused implementation that also exports JSON and visualization artifacts can create additional copies of sensitive document contents while the skill is marketed as redaction-oriented. In a privacy workflow, undeclared analysis and export side effects are hazardous because they may preserve or reveal the very data the user intended to remove.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

A PDF-focused implementation that also exports JSON and visualization artifacts can create additional copies of sensitive document contents while the skill is marketed as redaction-oriented. In a privacy workflow, undeclared analysis and export side effects are hazardous because they may preserve or reveal the very data the user intended to remove.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The file implements broad document reading and OCR extraction rather than redaction. In a privacy-redaction skill, unexpected full-text extraction of PDFs, Office documents, and images is dangerous because it increases the chance that sensitive content is collected, exposed to the model, or persisted beyond the user's intended operation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill invokes external desktop automation and office-conversion tools such as Word, PowerShell, AppleScript, and LibreOffice on untrusted documents. For a skill presented as a redaction toolkit, this is a substantial and unnecessary expansion of attack surface, including exposure to document parser exploits, macro/automation risks, and local side effects on the host.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/redact-document.py (reported line 107)May include surrounding context.

python
if not rules:
        raise ValueError(f"No valid replacement rules found in: {csv_path}")
    
    return rules


def replace_text_in_string(text: str, rules: Sequence[ReplacementRule]) -> Tuple[str, int]:

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/redact-image.py (reported line 274)May include surrounding context.

python
if not rules:
        raise ValueError(f"No valid replacement rules found in: {csv_path}")
    
    return rules


def replace_text_in_string(text: str, rules: Sequence[ReplacementRule]) -> Tuple[str, int]:

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/redact-pdf.py (reported line 285)May include surrounding context.

python
if not rules:
        raise ValueError(f"No valid replacement rules found in: {csv_path}")
    
    return rules


def replace_text_in_string(text: str, rules: Sequence[ReplacementRule]) -> Tuple[str, int]:

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/redact-presentation.py (reported line 98)May include surrounding context.

python
if not rules:
        raise ValueError(f"No valid replacement rules found in: {csv_path}")
    
    return rules


def replace_text_in_string(text: str, rules: Sequence[ReplacementRule]) -> Tuple[str, int]:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares executable scripts with file read/write, shell, and environment-dependent behavior, but does not define any tool scope such as permissions or allowed-tools. In a redaction skill that handles sensitive documents, this increases the chance of over-broad execution, unintended file access, or unsafe command use beyond what users would reasonably expect.

Content

No source excerpt is available for this finding.

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/read.py (reported line 443)May include surrounding context.

python
for path in libreoffice_paths:
        try:
            result = subprocess.run([path, "--version"], capture_output=True, text=True)
            if result.returncode == 0:
                return path
        except FileNotFoundError:

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/read.py (reported line 777)May include surrounding context.

python
for path in libreoffice_paths:
        try:
            result = subprocess.run([path, "--version"], capture_output=True, text=True)
            if result.returncode == 0:
                return path
        except FileNotFoundError:

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/redact-presentation.py (reported line 617)May include surrounding context.

python
for path in libreoffice_paths:
        try:
            result = subprocess.run([path, "--version"], capture_output=True, text=True)
            if result.returncode == 0:
                return path
        except FileNotFoundError:

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/read.py (reported line 457)May include surrounding context.

python
if system == "Windows":
        try:
            result = subprocess.run(
                [
                    "powershell",
                    "-NoProfile",

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/read.py (reported line 475)May include surrounding context.

python
if system == "Darwin":
        script = 'tell application id "com.microsoft.Word" to get name'
        try:
            result = subprocess.run(
                ["osascript", "-e", script],
                capture_output=True,
                text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
78% confidence
Finding

This PowerShell command constructs a script by interpolating the user-provided document path into PowerShell source code. Although single quotes are doubled, building script text from input is fragile and can enable script-breaking or unintended behavior, while also opening untrusted documents in Word automation, which materially expands risk.

Content

Scanner excerpt · scripts/read.py (reported line 510)May include surrounding context.

python
"  if ($word -ne $null) { $word.Quit() } "
                "}"
            )
            result = subprocess.run(
                ["powershell", "-NoProfile", "-Command", command],
                capture_output=True,
                text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
76% confidence
Finding

This AppleScript is dynamically built with a user-controlled file path and then executed by osascript. Escaping only double quotes is not robust AppleScript-safe parameterization, so malformed paths can break execution, and the code also opens untrusted files in Microsoft Word, increasing exposure to document-based attacks.

Content

Scanner excerpt · scripts/read.py (reported line 552)May include surrounding context.

python
end try
end tell
'''
            result = subprocess.run(
                ["osascript", "-e", script],
                capture_output=True,
                text=True,

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/read.py (reported line 576)May include surrounding context.

python
return None

    try:
        result = subprocess.run(
            [soffice, "--headless", "--convert-to", "pdf", "--outdir", str(pdf_path.parent), str(file_path)],
            capture_output=True,
            text=True,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The .ppt handler converts files in place by writing a derived .pptx into the source directory, which is outside the narrow expectations of a redaction workflow. This can create unauthorized artifacts, overwrite expectations about directory cleanliness, and leak the existence or contents of processed material through extra files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Writing converted .pptx files into the original file's directory without clear warning introduces unexpected filesystem side effects. In a privacy-focused skill, this is especially risky because derived files may persist in shared or synced locations and expose sensitive presentation content beyond the user's intent.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
87% confidence
Finding

The .ppt conversion launches LibreOffice on a user-supplied file and writes a converted file into the source directory. While not a shell-injection issue, invoking a complex external parser on untrusted documents and creating files beside the input can cause unintended filesystem side effects and increase exposure to parser vulnerabilities in LibreOffice.

Content

Scanner excerpt · scripts/read.py (reported line 793)May include surrounding context.

python
output_dir = file_path.parent
    try:
        result = subprocess.run(
            [soffice, "--headless", "--convert-to", "pptx", "--outdir", str(output_dir), str(file_path)],
            capture_output=True,
            text=True,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The tool can save extracted document content to an arbitrary user-specified path. In the context of a redaction skill handling sensitive documents, this creates an exfiltration/persistence channel for raw extracted PII that is unrelated to the advertised masking purpose.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The function documentation says it redacts a 'Word document', and the CLI later accepts both .docx and .doc extensions, but the code loads the file through docx.Document, which is for OOXML .docx files rather than legacy binary .doc files. This is an intent/documentation mismatch that could mislead callers about what formats are actually supported.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.