Back to skill

Security audit

Pdf Vision

Security checks for vulnerabilities and agentic risk

Overview

This PDF extraction skill mostly does what it claims, but it also ships unrelated GitHub account code and leaves sensitive document artifacts in shared temporary files.

Review before installing. Use only with PDFs you are allowed to send to third-party vision APIs, remove or ignore the GitHub repository helper, avoid the legacy shell script, and use a private temporary directory or cleanup process for document images and API payloads.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract_pdf_vision.sh:85
Finding
Arbitrary Python Code Execution Through Unsafe Argument Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_pdf_vision.sh`, lines 85-113; related unsafe interpolation at lines 129-142 and 193-198 **Vulnerability Type**: Command injection through generated Python source **Risk Level**: High ### Vulnerable Code ```bash python3 -c " import pypdfium2 as pdfium import os # Open PDF pdf = pdfium.PdfDocument('$PDF_PATH') page_count = len(pdf) # Validate page number target_page = $PAGE_NUMBER if target_page == 0: target_page = 0 # First page (0-indexed) else: target_page = target_page - 1 # Convert to 0-indexed if target_page >= page_count or target_page < 0: raise ValueError(f'Invalid page number. PDF has {page_count} pages.') # Render page as image page = pdf[target_page] pil_image = page.render( scale=2, # 2x zoom for better quality rotation=0, ).to_pil() # Save image pil_image.save('$IMAGE_PATH') print(f'PDF page converted to image: $IMAGE_PATH') print(f'Total pages in PDF: {page_count}') " ``` The fallback configuration and response parsers use the same unsafe construction: ```bash BASE_URL=$(python3 -c " import json with open('$CONFIG_FILE', 'r') as f: config = json.load(f) print(config.get('models', {}).get('providers', {}).get('openai', {}).get('baseUrl', '')) ") ``` ```bash RESPONSE_TEXT=$(python3 -c " import json with open('$RESPONSE_PATH', 'r') as f: response = json.load(f) print(response['choices'][0]['message']['content']) ") ``` ### Technical Analysis Values originating from `--pdf-path`, `--page`, `--config`, and `--temp-dir` are interpolated directly into source code passed to `python3 -c`. Shell quoting does not protect the resulting Python program. A value containing quote characters and valid Python syntax can terminate the intended string or expression and append attacker-controlled statements. The vulnerable script does not constrain the page argument to an integer before embedding it as executable syntax. Path arguments are also inse ...[truncated 1463 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove dynamically generated Python source from the shell implementation. - Pass all data through positional arguments to a fixed Python program: ```bash python3 helper.py \ --pdf-path "$PDF_PATH" \ --page "$PAGE_NUMBER" \ --image-path "$IMAGE_PATH" ``` - Access arguments through `argparse` or `sys.argv`; never interpolate them into executable source. - Validate `PAGE_NUMBER` with a strict numeric expression before use: ```bash [[ "$PAGE_NUMBER" =~ ^[0-9]+$ ]] || { echo "Invalid page number" >&2 exit 1 } ``` - Perform JSON parsing in the existing Python implementation rather than through generated `python3 -c` commands. - Prefer removing the duplicate shell implementation and retaining only `pdf_vision_enhanced.py`, which uses structured Python arguments and does not construct executable source from the supplied paths. - Add regression tests containing quotes, backslashes, newlines, semicolons, and Python syntax in every path and prompt argument. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extract_pdf_vision.sh:74
Finding
Sensitive Document Data Stored in Predictable Shared Temporary Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_pdf_vision.sh`, lines 74-80 and 211-212; `scripts/pdf_vision_enhanced.py`, lines 68-69 and 126-130 **Vulnerability Type**: Unsafe temporary-file handling and plaintext sensitive-data retention **Risk Level**: Medium ### Vulnerable Code The shell implementation uses fixed paths and explicitly leaves cleanup disabled: ```bash # Create temporary files IMAGE_PATH="$TEMP_DIR/pdf_vision_page.png" PAYLOAD_PATH="$TEMP_DIR/pdf_vision_payload.json" RESPONSE_PATH="$TEMP_DIR/pdf_vision_response.json" # Clean up existing temp files rm -f "$IMAGE_PATH" "$PAYLOAD_PATH" "$RESPONSE_PATH" ``` ```bash # Step 8: Cleanup (optional - keep temp files for debugging if needed) # rm -f "$IMAGE_PATH" "$PAYLOAD_PATH" "$RESPONSE_PATH" ``` The Python implementation also uses predictable names: ```python image_path = Path(temp_dir) / "pdf_vision_page.png" pil_image.save(image_path) ``` ```python payload_path = Path(temp_dir) / f"pdf_vision_payload_{provider}_{model.replace('/', '_')}.json" response_path = Path(temp_dir) / f"pdf_vision_response_{provider}_{model.replace('/', '_')}.json" with open(payload_path, 'w') as f: json.dump(payload, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis Both implementations default to the globally shared `/tmp` directory and use deterministic filenames. The artifacts include: - A rendered image of the selected PDF page. - A complete API request containing the Base64-encoded page and user prompt. - The API response containing extracted document text. The Python implementation does not create a private per-run directory, request restrictive file modes, or remove the files after use. The shell implementation removes old paths before processing but recreates them under the same known names and leaves the resulting files behind. Depending on the host's umask, directory permissions, symlink protections, and whether another process runs under the same user, this creat ...[truncated 1542 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use a unique, private directory for every run: ```python import tempfile with tempfile.TemporaryDirectory(prefix="pdf-vision-") as temp_dir: image_path = Path(temp_dir) / "page.png" payload_path = Path(temp_dir) / "payload.json" response_path = Path(temp_dir) / "response.json" ``` - Ensure temporary directories and files are owner-only, using directory mode `0700` and file mode `0600`. - Delete all artifacts in a `finally` block, including when conversion or API calls fail. - Avoid storing the complete Base64 request payload when possible. Submit it directly through the HTTP client. - Make artifact retention an explicit debugging option that warns the user and writes to a user-selected private directory. - Use atomic creation with exclusive-create semantics and reject symbolic links. - Generate unique names for every provider, page, process, and invocation. - Add concurrency and symlink-resistance tests. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/create_github_repo.py:15
Finding
Out-of-Scope GitHub Token Discovery and Authenticated Account Operation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/create_github_repo.py`, lines 15-58 **Vulnerability Type**: Excessive credential access and violation of least privilege **Risk Level**: Medium ### Vulnerable Code ```python # Get GitHub token from environment github_token = os.environ.get('GITHUB_TOKEN') if not github_token: # Try to read from .bashrc try: with open(os.path.expanduser('~/.bashrc'), 'r') as f: for line in f: if 'GITHUB_TOKEN' in line: # Extract token from export line if '"' in line: token = line.split('"')[1] elif "'" in line: token = line.split("'")[1] else: token = line.split('=')[1].strip() github_token = token break except FileNotFoundError: pass if not github_token: print("Error: GITHUB_TOKEN not found in environment or ~/.bashrc") return False # GitHub API endpoint url = "https://api.github.com/user/repos" # Repository data data = { "name": repo_name, "description": description, "private": private, "auto_init": True, "gitignore_template": "Python" } # Headers headers = { "Authorization": f"token {github_token}", "Accept": "application/vnd.github.v3+json" } try: response = requests.post(url, json=data, headers=headers) ``` ### Technical Analysis The declared Skill functionality is PDF page rendering and vision-based text extraction. Creating GitHub repositories is unrelated to that runtime purpose. The bundled helper searches both the process environment and the user's general shell startup file for a GitHub credential. Reading `~/.bashrc` to scrape credentials broadens the Skill's access beyond what PDF extraction requires. The parser is also fragile: it accepts any line containing `GITHUB_TOKEN` and extracts content based on simple qu ...[truncated 1425 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `scripts/create_github_repo.py` from the distributed PDF extraction Skill. - If repository creation is retained as a separate administrative utility: - Require an explicit user action and clear confirmation before making the request. - Do not search `~/.bashrc` or unrelated configuration files. - Use GitHub CLI authentication or a dedicated credential provider. - Require a fine-grained token limited to repository creation for the intended account. - Validate repository name, visibility, and description before submission. - Clearly display the target GitHub account and requested operation before proceeding. - Keep publishing and development utilities outside the runtime Skill package. - Add package review checks that reject unrelated credential-accessing utilities. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extract_pdf_vision.sh:161
Finding
Malformed or Manipulated API Payload Through Unescaped Prompt Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_pdf_vision.sh`, lines 161-181 **Vulnerability Type**: JSON injection and unsafe data serialization **Risk Level**: Medium ### Vulnerable Code ```bash cat > "$PAYLOAD_PATH" << EOF { "model": "qwen3-vl-plus", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "$PROMPT" }, { "type": "image_url", "image_url": { "url": "data:image/png;base64,$IMAGE_BASE64" } } ] } ], "max_tokens": 2000 } EOF ``` ### Technical Analysis `PROMPT` is inserted directly into a JSON string without JSON encoding. Shell double-quote handling does not escape the value for JSON. Prompt content containing double quotes, backslashes, control characters, or newlines can invalidate the payload or terminate the intended JSON string and inject additional JSON fields or objects. This is a data-serialization vulnerability rather than shell command injection: ordinary command substitution syntax contained inside the expanded variable is not recursively executed by the shell. The security issue is that attacker-controlled content can alter the structure sent to the remote model API. ### Attack Path 1. An attacker controls or influences the value supplied through `--prompt`. 2. The attacker includes JSON metacharacters that close the expected `"text"` value. 3. The heredoc writes the attacker-controlled characters directly into the request document. 4. The resulting payload is either malformed, causing a denial of service, or remains valid with attacker-selected structural changes. 5. `curl` submits the modified payload to the configured vision endpoint. ### Impact Assessment Exploitation can: - Cause extraction requests to fail consistently. - Change model request fields when a syntactically valid injected payload can be formed. - Alter message content or token-related options ac ...[truncated 262 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Construct the request with a JSON serializer rather than a heredoc: ```python payload = { "model": "qwen3-vl-plus", "messages": [{ "role": "user", "content": [ {"type": "text", "text": prompt}, { "type": "image_url", "image_url": { "url": f"data:image/png;base64,{image_base64}" } } ] }], "max_tokens": 2000 } json.dump(payload, output_file) ``` - If the shell implementation must remain, use `jq --arg` or pass the prompt to a fixed Python JSON serializer. - Validate reasonable prompt length to limit accidental cost and resource consumption. - Add tests for prompts containing quotes, backslashes, Unicode, tabs, carriage returns, and multiline text. - Consolidate request construction in `pdf_vision_enhanced.py`, which already uses `json.dump`. ]]>

T08 · Insecure Dependencies

Note
Location
README.md:40
Finding
Unpinned Python Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `README.md`, lines 40-43 **Vulnerability Type**: Unconstrained third-party dependency **Risk Level**: Low ### Vulnerable Code ```bash ### Python Dependencies ```bash pip3 install pypdfium2 ``` ``` ### Technical Analysis The documented installation command retrieves the latest available `pypdfium2` release without a version constraint, lock file, or integrity hash. The package name matches the library imported by the implementation, and the audit found no evidence of typosquatting or an intentionally malicious dependency. However, unconstrained installation makes the build non-reproducible and automatically trusts future upstream releases. A compromised upstream account, malicious release, or incompatible update could introduce unreviewed code into the Skill environment. ### Attack Path 1. A user follows the documented installation command. 2. `pip` resolves the newest available package version from its configured package index. 3. A future compromised, malicious, or incompatible release is selected because no reviewed version is pinned. 4. Package installation or later import executes unreviewed package code under the installing or Skill-running account. This path depends on an upstream or package-index compromise; no such compromise was identified during this audit. ### Impact Assessment The possible impact is code execution with the privileges used for installation or Skill execution, as well as availability failures caused by incompatible releases. The practical risk is lower than the direct injection flaws because exploitation requires a supply-chain event outside this repository. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin a reviewed release in a dependency file: ```text pypdfium2==<reviewed-version> ``` - Generate and verify cryptographic hashes, for example with a hash-locked requirements file. - Install dependencies into an isolated virtual environment rather than the user's global Python environment. - Record the expected package index and prohibit unexpected extra indexes. - Use an automated dependency update process that reviews release notes and reruns security and functional tests before changing the pin. - Include `requests` in the dependency manifest if the unrelated GitHub helper is retained; otherwise remove that helper. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (28)

Tainted flow: 'headers' from os.environ.get (line 51, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}
    
    try:
        response = requests.post(url, json=data, headers=headers)
        
        if response.status_code == 201:
            repo_info = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

External Model or Provider Selection

High
Category
Excessive Agency
Content
### Specific Model Selection
```bash
# Use free GLM-4.6V-Flash model
./pdf_vision.py --pdf-path document.pdf --model glm-4.6v-flash

# Use premium Qwen3-VL-Plus model  
./pdf_vision.py --pdf-path document.pdf --model qwen3-vl-plus
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill advertises automatic fallback across multiple providers and broader PDF extraction behavior, but the finding indicates implementation only supports a single hardcoded provider/model path and one-page processing. Security-relevant documentation mismatches can mislead operators about reliability, data flows, and failure handling, causing unsafe assumptions in production.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill advertises automatic fallback across multiple providers and broader PDF extraction behavior, but the finding indicates implementation only supports a single hardcoded provider/model path and one-page processing. Security-relevant documentation mismatches can mislead operators about reliability, data flows, and failure handling, causing unsafe assumptions in production.

External Model or Provider Selection

High
Category
Excessive Agency
Content
./scripts/pdf_vision.py --pdf-path document.pdf --model openai/qwen3-vl-plus

# Short form (auto-detects provider)
./scripts/pdf_vision.py --pdf-path document.pdf --model glm-4.6v-flash
```

### Structured Data Extraction
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

External Model or Provider Selection

High
Category
Excessive Agency
Content
### Structured Data Extraction
```bash
./scripts/pdf_vision.py --pdf-path invoice.pdf --prompt "Extract as JSON: vendor, date, total" --model glm-4.6v-flash
```

### Multi-page PDF Handling
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
This file implements GitHub repository creation, which is unrelated to the stated PDF OCR/vision purpose of the skill. Unrelated account-management capabilities increase attack surface and can be used to trigger external side effects using the user's credentials, making the skill materially more dangerous in context.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The code includes GitHub account and repository management capability without justification from the skill's advertised function. In a PDF-processing skill, this capability is out of scope and could create or modify resources in the user's GitHub account if a token is present, which is a significant privilege expansion.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The manifest describes multi-provider support with automatic fallback across Xflow and ZhipuAI models, but this script hardcodes a single model (qwen3-vl-plus) and performs exactly one POST to one configured /chat/completions endpoint. There is no provider selection, no retry across alternative providers/models, and no fallback logic if the request fails.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
enhanced_script = os.path.join(script_dir, "pdf_vision_enhanced.py")

# Execute the enhanced script with all arguments
os.execv(sys.executable, [sys.executable, enhanced_script] + sys.argv[1:])
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The README describes extracting content from scanned PDFs with third-party vision APIs but does not clearly warn that PDF page images and extracted content are transmitted off-host to external providers. Users may unknowingly send sensitive documents such as invoices, research papers, or internal records to remote services, creating privacy, compliance, and data-governance risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill documentation describes capabilities that involve shell execution, file access, environment/config access, and network calls, but it declares no explicit tool scope or permission boundaries. In an agent setting, that omission increases the chance the skill can be invoked with broader-than-expected authority, making misuse or accidental overreach more likely.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This skill sends document images and prompts to third-party vision APIs, but the description does not clearly warn users that potentially sensitive document contents leave the local environment. That creates a data exposure risk, especially for scanned contracts, IDs, invoices, medical records, or other regulated content.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The documentation states that converted page images, API payloads, and API responses are written to /tmp, but it does not adequately warn that these files may contain sensitive document data and extracted content. Temporary files in shared or weakly controlled environments can be read by other processes/users or persist longer than expected.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
"""
Create GitHub repository using GitHub API and your token
"""

import os
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The script silently reads a GitHub token from ~/.bashrc if GITHUB_TOKEN is not set, harvesting credentials from a shell startup file without prior disclosure or consent. This is dangerous because it broadens credential access beyond explicit runtime input and normalizes secret scraping behavior inside an unrelated skill.

External Transmission

Medium
Category
Data Exfiltration
Content
return False
    
    # GitHub API endpoint
    url = "https://api.github.com/user/repos"
    
    # Repository data
    data = {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}
    
    try:
        response = requests.post(url, json=data, headers=headers)
        
        if response.status_code == 201:
            repo_info = response.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The header comment says the script extracts text using the Xflow vision API, suggesting a specific provider integration. In practice, lines L121-L122 and L129-L135 load models.providers.openai.baseUrl and apiKey from a generic config, and L180 posts to that configured endpoint, so the documented intent is narrower and more specific than the actual behavior.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script sends rendered PDF page contents to a remote vision API and stores intermediate image/payload/response files in /tmp without clearly warning the user about external transmission or residual local artifacts. In a document-processing skill, this matters because scanned PDFs commonly contain sensitive personal, legal, financial, or proprietary data.

External Transmission

Medium
Category
Data Exfiltration
Content
# Step 5: Make API call
echo "Calling Xflow API..."
curl -X POST "$BASE_URL/chat/completions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d @"$PAYLOAD_PATH" \
Confidence
97% confidence
Finding
This line performs external transmission of extracted document imagery and prompt data to whatever OpenAI-compatible base URL is configured. In the context of a PDF vision extraction skill, such transmission is expected functionality, but it still creates real confidentiality risk if users process sensitive scanned documents or misconfigure the endpoint to an untrusted service.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
This skill sends rendered PDF page images to third-party vision APIs, which may contain confidential or regulated data, but it provides no explicit consent gate or warning about external disclosure. In the context of scanned-document extraction, users may reasonably expect local OCR-like processing, making silent transmission materially risky.

Session Persistence

Medium
Category
Rogue Agent
Content
with open(image_path, "rb") as image_file:
            image_base64 = base64.b64encode(image_file.read()).decode('utf-8')
        
        # Create payload based on provider
        if provider == "openai":
            # Xflow/OpenAI format
            payload = {
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The skill writes full request and response artifacts to predictable files in the temporary directory, including base64-encoded page images and extracted document content. On multi-user systems or unsafe temp-dir configurations, this can expose sensitive PDF contents and API interaction data to other local users or leave recoverable artifacts after execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--show-error"
        ]
        
        result = subprocess.run(curl_cmd, capture_output=True, text=True)
        if result.returncode != 0:
            error_msg = result.stderr if result.stderr else result.stdout
            raise RuntimeError(f"API call failed for {provider}/{model}: {error_msg}")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.