Back to skill

Security audit

PaddleOCR

Security checks for vulnerabilities and agentic risk

Overview

The skill appears purpose-built for OCR, but it uploads sensitive documents to a configured OCR service, keeps local archives by default, and has unsafe file/network handling that users should review before installing.

Review this skill carefully before installation, especially if you handle privileged legal, medical, financial, or evidence files. Use only a trusted PaddleOCR endpoint, understand that document contents may leave the machine, prefer limited page ranges, consider --no-archive for sensitive files, and avoid output paths where an existing *_images directory contains important data.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/convert.py:133
Finding
Response-Controlled Image URLs Enable Blind SSRF and Unbounded Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.py:133-142` **Vulnerability Type**: Server-Side Request Forgery and uncontrolled resource download **Risk Level**: Medium ### Complete Code Snippet ```python target_path = images_dir / filename if str(image_data).startswith(("http://", "https://")): urllib.request.urlretrieve(str(image_data), target_path) else: target_path.write_bytes(decode_base64_image(str(image_data))) ``` ### Technical Analysis The `image_data` value comes from the remote OCR provider's response. When that value begins with `http://` or `https://`, the application downloads it with `urllib.request.urlretrieve()` without validating the destination host. The implementation does not: - Restrict downloads to documented PaddleOCR image hosts. - Reject loopback, private, link-local, multicast, or reserved IP addresses. - Revalidate the destination after DNS resolution. - Validate redirect targets. - Require HTTPS. - Apply an explicit download timeout. - Limit the number of downloaded bytes. - Verify that the response is an image before storing it. A malicious or compromised OCR endpoint can consequently direct the client to internal services such as `127.0.0.1`, RFC1918 network addresses, or cloud metadata endpoints. Redirects and DNS rebinding may also bypass superficial hostname checks unless every resolved and redirected destination is validated. Because the response is written to disk rather than returned to the OCR provider, the demonstrated issue is primarily blind SSRF rather than confirmed direct response exfiltration. Nevertheless, response timing, conversion success, generated archives, and locally accessible output files may reveal whether targeted resources exist. ### Attack Path 1. An attacker controls or compromises the OCR endpoint configured through `PADDLEOCR_DOC_PARSING_API_URL`. 2. The user submits a document for conversion. 3. Th ...[truncated 1337 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer inline image data returned directly by the trusted OCR provider instead of retrieving secondary URLs. 2. If remote image downloads are required, maintain an explicit allowlist of documented provider hostnames. 3. Require HTTPS for non-local remote resources. 4. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved addresses for both IPv4 and IPv6. 5. Disable redirects or apply the same scheme, hostname, DNS, and IP validation to every redirect target. 6. Use a streaming HTTP client with explicit connection, read, and total timeouts. 7. Enforce a strict maximum response size and abort the transfer when the limit is exceeded. 8. Validate `Content-Type`, decode the image with a hardened image parser, and reject non-image content. 9. Limit the number and aggregate size of images accepted from each OCR response. 10. Document the secondary download behavior as part of the Skill's network and privacy boundary. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/convert.py:113
Finding
Derived Image Output Directory Is Recursively Deleted Without Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.py:113-120` **Vulnerability Type**: Unsafe recursive deletion of a user-influenced path **Risk Level**: Medium ### Complete Code Snippet ```python def save_images( batch_outputs: list[dict[str, Any]], images_dir: Path, ) -> list[dict[str, str]]: saved: list[dict[str, str]] = [] if images_dir.exists(): shutil.rmtree(images_dir) if not any(batch["images"] for batch in batch_outputs): return saved ``` The deleted path is derived from the selected Markdown output path: ```python def resolve_images_dir(markdown_path: Path) -> Path: return markdown_path.with_name(f"{markdown_path.stem}_images") ``` ### Technical Analysis Before checking whether the OCR response contains any images, `save_images()` recursively deletes the existing `images_dir`. The directory is derived from the user-controlled `--output` argument by appending `_images` to the Markdown file stem. There is no ownership marker, path-safety check, confirmation prompt, backup, or explicit overwrite option. The code cannot distinguish a directory previously created by this Skill from an unrelated directory that happens to have the derived name. The deletion occurs even when `batch_outputs` contains no images because the image-presence check follows `shutil.rmtree()`. It also occurs before replacement image writes complete, so a subsequent decoding or download failure can leave the previous directory permanently removed. ### Attack Path 1. A directory containing valuable files already exists at a path such as `/work/report_images`. 2. The Skill is invoked with `--output /work/report.md`, either intentionally or through an automated workflow. 3. `resolve_images_dir()` derives `/work/report_images`. 4. `save_images()` sees that the directory exists and recursively removes it with `shutil.rmtree()`. 5. The prior contents are lost, even if the OCR response contains no images or a later image ...[truncated 760 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Check whether the OCR result contains images before performing any filesystem cleanup. 2. Refuse to overwrite an existing image directory by default. 3. Require an explicit `--force` or `--replace-images` option before deleting existing content. 4. Add a Skill-specific ownership marker and only clean directories previously created by the same application. 5. Prefer a fresh, uniquely named output directory for each conversion. 6. If stable directory names are required, write into a temporary sibling directory and atomically replace the old directory only after all images have been validated and saved successfully. 7. Delete only files recorded in a prior application-generated manifest rather than recursively deleting the entire directory. 8. Resolve the path and apply safety checks that reject filesystem roots, home directories, the Skill root, and other protected locations. 9. Clearly document overwrite behavior in the command help and Skill documentation. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/convert.py:1
Finding
Runtime Dependencies Are Loosely Constrained and Not Integrity-Locked<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.py:1-7` **Vulnerability Type**: Unpinned runtime dependency resolution **Risk Level**: Low The same pattern also appears in: - `scripts/layout_caller.py:1-6` - `scripts/smoke_test.py:1-6` - `scripts/optimize_file.py:1-6` - `scripts/split_pdf.py:1-6` ### Complete Code Snippet ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.9" # dependencies = [ # "httpx>=0.27.0", # "pypdfium2>=4.30.0", # ] # /// ``` Other scripts similarly specify dependencies such as: ```python # dependencies = [ # "Pillow>=10.0.0", # ] ``` ### Technical Analysis The scripts are designed to run through `uv run`, which may resolve and install dependencies before executing the application. The dependency declarations use only lower version bounds, such as `httpx>=0.27.0`, and the project does not provide a reviewed lockfile or package hashes in the audited directory. As a result, the exact code executed by a future invocation may differ from the code used during this audit. Any later package version satisfying the lower bound may be selected. This introduces supply-chain and reproducibility risk, particularly because imported Python packages execute initialization code with the same privileges as the Skill process. No evidence was found that the named packages are currently malicious, misspelled, or sourced from an unauthorized registry. The finding concerns unsafe dependency governance rather than a confirmed malicious package. ### Attack Path 1. A future compatible release of one of the declared packages is compromised, malicious, or otherwise unsafe. 2. A user invokes a script through `uv run` in an environment that does not already have a locked cached resolution. 3. The dependency resolver selects and downloads the future release because it satisfies the declared lower bound. 4. Python imports the downloaded package. 5. Package initialization or later library execution ...[truncated 730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to an exact reviewed version. 2. Generate and commit a lockfile that includes transitive dependency versions. 3. Use package hash verification where supported. 4. Configure trusted package indexes explicitly and disallow unexpected extra indexes. 5. Update dependencies through a controlled review process with automated vulnerability and provenance checks. 6. Test locked dependency updates before deployment. 7. Consider packaging the Skill in a reproducible environment or container rather than resolving dependencies during each invocation. 8. Document the network access and package installation that may occur when `uv run` initializes the environment. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (34)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The documented behavior expands beyond the stated purpose by supporting remote file URLs and sending document contents to an external API using configured credentials, while the high-level description frames the skill as local OCR-to-Markdown processing. That mismatch can mislead users into exposing confidential legal or medical documents to third parties without informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented behavior expands beyond the stated purpose by supporting remote file URLs and sending document contents to an external API using configured credentials, while the high-level description frames the skill as local OCR-to-Markdown processing. That mismatch can mislead users into exposing confidential legal or medical documents to third parties without informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented behavior expands beyond the stated purpose by supporting remote file URLs and sending document contents to an external API using configured credentials, while the high-level description frames the skill as local OCR-to-Markdown processing. That mismatch can mislead users into exposing confidential legal or medical documents to third parties without informed consent.

Credential Access

High
Category
Privilege Escalation
Content
2. 进入对应模型的 API 页面
3. 在示例代码中复制:
   - `API_URL`
   - `Access Token`

### 配置方式
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. 进入对应模型的 API 页面
3. 在示例代码中复制:
   - `API_URL`
   - `Access Token`

### 配置方式
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd paddle-ocr/config
cp .env.example .env
nano .env
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd paddle-ocr/config
cp .env.example .env
nano .env
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Ae1

High
Category
analysis-evasion
Content
/usr/bin/osascript -l JavaScript scripts/convert.js "/path/to/legal-document.pdf"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
def get_config_path() -> Path:
    return get_skill_root() / "config" / ".env"


def read_env_file(path: Path) -> dict[str, str]:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def get_config_path() -> Path:
    return get_skill_root() / "config" / ".env"


def read_env_file(path: Path) -> dict[str, str]:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def get_runtime_config() -> dict[str, Any]:
    file_env = read_env_file(get_config_path())
    merged_env = {**file_env, **os.environ}

    api_url = first_non_empty(merged_env, "PADDLEOCR_DOC_PARSING_API_URL")
    access_token = first_non_empty(merged_env, "PADDLEOCR_ACCESS_TOKEN")
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill documents capabilities that imply reading files, writing archives, using environment-based credentials, and making network calls, but it does not declare any explicit tool scope or permission boundaries. In an agent setting, missing scope declarations can cause overbroad access and make it harder for users or the runtime to understand that sensitive legal documents and tokens may be accessed and transmitted.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description emphasizes OCR and archiving benefits but does not prominently warn that sensitive documents may be transmitted to an external OCR provider and retained locally by default. For legal, medical, and evidence materials, this omission materially increases the chance of inadvertent privacy, confidentiality, or compliance violations.

Context-Inappropriate Capability

Medium
Confidence
83% confidence
Finding
The documented workflow allows submitting document URLs to a remote parsing API, which broadens the trust boundary from local document conversion to externally fetched or relayed content. In the context of legal and medical records, this creates additional confidentiality, provenance, and SSRF-style risk surfaces if URLs are accepted without strong restrictions.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation explicitly describes persistent archiving of the original input, extracted full text, images, batch artifacts, metadata, source path, and file hash, but provides no warning, consent flow, retention control, or privacy safeguards. In this skill’s context—legal PDFs, medical records, and evidence scans—the archived data is likely to contain highly sensitive personal, legal, or regulated information, so silent retention materially increases confidentiality and compliance risk.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The manifest describes a PaddleOCR skill for converting local PDFs/images to Markdown, but this wrapper implements that by constructing and executing shell commands via `doShellScript`, invoking `/bin/zsh`, `dirname`, `uv`, and Python. Spawning general shell commands is a broader capability than the stated OCR/Markdown conversion purpose and is not explicitly justified by the manifest text.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The script is presented as operating on local PDFs/images, but during image output handling it will fetch arbitrary remote URLs returned in OCR results via urllib.request.urlretrieve. This creates an unexpected outbound network path and can expose the host to SSRF-style access to internal resources or silent downloads from untrusted locations.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
Remote download capability is not necessary for the stated local OCR conversion purpose and expands the attack surface. If the upstream parser or a crafted document can influence image references, the script may retrieve attacker-controlled content or access internal endpoints without the user's awareness.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The conversion flow sends document-derived content to an external OCR/parsing API through parse_document, yet the CLI does not provide an explicit warning or consent mechanism. In this skill's context—legal PDFs, medical records, and evidence scans—the transmitted data is likely highly sensitive, making undisclosed network exfiltration a significant privacy and compliance risk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script archives the original input, extracted markdown, images, and metadata by default unless --no-archive is supplied. For a skill intended for legal case files, medical records, and evidence documents, silent retention of sensitive materials on disk materially increases confidentiality, forensic, and compliance exposure if the machine is shared or later compromised.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
Manifest 说明技能“默认将本地 PDF 或图片转换为 Markdown”,但该调用脚本的稳定输出是 JSON envelope,并且默认写入 `.json` 文件。虽然底层解析结果可能间接包含 Markdown,但从该文件可见的实际接口行为与“默认输出 Markdown”不一致。

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
Manifest 描述强调默认处理本地 PDF 或图片,并未说明该技能还可接收远程文件 URL。此脚本通过 `--file-url` 暴露远程输入能力,属于对外部资源访问的额外行为,与面向本地文档 OCR 的描述存在语义偏差。

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script writes OCR results by default into a predictable temp-directory subtree without explicit user acknowledgement, which can persist sensitive legal, medical, or evidentiary data on disk. In the context of this skill, the extracted content is likely highly sensitive, so silent persistence increases the risk of local disclosure, retention beyond user intent, and accidental exposure to other processes or operators with filesystem access.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The file contains multiple natural-language strings presented to users exclusively in Chinese, such as configuration and runtime error messages. Because no language selection, opt-in, or documented region-specific justification is provided, this creates a language/locale policy issue.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code sends the assembled payload to a remote API, and that payload can include either a base64-encoded local document or a user-supplied file URL. The file contains no confirmation prompt, print/log disclosure, or comment/docstring warning that document data will be transmitted to an external service.

Static analysis

No suspicious patterns detected.