Back to skill

Security audit

MinerU OCR Local & API

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its MinerU OCR purpose, but API mode handles credentials, document uploads, remote URLs, and output paths too broadly for sensitive documents.

Install only if you trust the MinerU API endpoint and are comfortable with selected documents being uploaded in API mode. Prefer --mode local for sensitive files, avoid custom MINERU_API_BASE_URL values unless you fully control the endpoint, use session-only tokens where possible, and set an explicit download directory for outputs.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lib.py:129
Finding
Bearer Token Disclosure Through an Unrestricted API Base URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib.py:129-132` and `scripts/lib.py:210-216` **Vulnerability Type**: Unrestricted credential destination and plaintext transport **Risk Level**: High ### Vulnerable Code ```python base_url = _get_env("MINERU_API_BASE_URL") or DEFAULT_BASE_URL if not urlparse(base_url).scheme: base_url = f"https://{base_url}" base_url = base_url.rstrip("/") ``` ```python def _make_client(config: Config) -> httpx.Client: return httpx.Client( timeout=config.timeout, headers={ "Authorization": f"Bearer {config.token}", "Accept": "application/json", "User-Agent": "mineru-ocr-local-api-skill/1.1.0", }, ) ``` ### Technical Analysis `MINERU_API_BASE_URL` accepts an arbitrary scheme and host. HTTPS is added only when the supplied value has no scheme; an explicitly supplied `http://` URL remains permitted. The HTTP client attaches `MINERU_API_TOKEN` as a bearer token to every API request made through that client. Consequently, an attacker who can influence the environment or runtime configuration can redirect authenticated API requests to an attacker-controlled server. If HTTP is selected, the token can also be exposed in plaintext to parties capable of observing or modifying network traffic. Custom API endpoints are a documented feature, but the implementation does not enforce transport security, verify that the destination is trusted, or warn that the production credential will be sent to the configured host. ### Attack Path 1. An attacker influences `MINERU_API_BASE_URL`, such as through a poisoned environment, deployment configuration, or wrapper script. 2. The value is set to `https://attacker.example` or `http://attacker.example`. 3. A user or agent runs the skill in API mode with `MINERU_API_TOKEN` configured. 4. `_make_client()` installs the token in the default `Authorization` header. 5. The skill submits a request to the attacker-control ...[truncated 736 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https` for all API base URLs and reject `http`, file, or other schemes. 2. Allowlist the official MinerU API host by default. 3. If custom endpoints are required, place them behind an explicit opt-in configuration and display a clear warning that credentials will be sent to that host. 4. Validate normalized hostnames and ports before creating the authenticated client. 5. Create authentication headers per request only after destination validation, rather than installing the bearer token as an unconditional client-wide header. 6. Reject URLs containing embedded credentials, unexpected ports, or ambiguous hostname encodings. 7. Rotate any MinerU token that may have been used with an untrusted base URL. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lib.py:658
Finding
Server-Controlled Upload and Download URLs Permit SSRF and Document Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib.py:658-662`, `scripts/lib.py:758-766`, and `scripts/lib.py:821-835` **Vulnerability Type**: Server-Side Request Forgery and arbitrary file-content upload destination **Risk Level**: High ### Vulnerable Code ```python upload_url = _extract_upload_url(batch_response) if not upload_url: raise RuntimeError("MinerU upload flow did not return a file upload URL.") _upload_file(upload_url=upload_url, path=path) return batch_response, batch_id ``` ```python try: with httpx.stream("GET", full_zip_url, timeout=DEFAULT_TIMEOUT, follow_redirects=True) as response: if response.status_code >= 400: raise RuntimeError( f"Failed to download MinerU archive ({response.status_code}): {response.text[:200]}" ) with zip_path.open("wb") as handle: for chunk in response.iter_bytes(): handle.write(chunk) ``` ```python def _upload_file(*, upload_url: str, path: Path) -> None: with path.open("rb") as handle: try: response = httpx.put( upload_url, content=handle.read(), timeout=DEFAULT_TIMEOUT, follow_redirects=True, ) except httpx.TimeoutException as exc: raise RuntimeError(f"Timed out while uploading {path.name} to MinerU") from exc except httpx.RequestError as exc: raise RuntimeError(f"Failed to upload {path.name} to MinerU: {exc}") from exc if response.status_code not in (200, 201, 204): raise RuntimeError( f"MinerU upload failed ({response.status_code}): {response.text[:200]}" ) ``` ### Technical Analysis The skill trusts `upload_url` and `full_zip_url` values returned in API responses and performs requests to them without validating: - The URL scheme - The destination hostname or port - Whether the resolved address is loopback, link-local, private, or r ...[truncated 2536 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only HTTPS upload and download URLs. 2. Maintain an allowlist of documented MinerU object-storage and archive hosts. 3. Resolve destination hostnames and reject loopback, link-local, private, multicast, unspecified, and reserved IP addresses for both IPv4 and IPv6. 4. Revalidate the destination after every DNS resolution and redirect to mitigate redirects and DNS rebinding. 5. Disable automatic redirects or implement a bounded redirect handler that validates each target before following it. 6. Reject URLs containing embedded credentials, nonstandard schemes, fragments, or unexpected ports. 7. Apply equivalent validation to the `curl` fallback, or remove that fallback unless it can enforce the same policy. 8. Stream file uploads instead of `handle.read()` to avoid loading the entire document into memory; this does not fix SSRF but improves resource safety. 9. Document the approved upload and download domains and fail closed when the API returns an unexpected destination. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lib.py:744
Finding
Remote Task Identifiers Permit Artifact Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib.py:367-370` and `scripts/lib.py:744-756` **Vulnerability Type**: Path traversal through an untrusted filesystem path component **Risk Level**: High ### Vulnerable Code ```python artifact_paths = _download_and_extract( client=client, full_zip_url=full_zip_url, task_id=artifacts.get("task_id") or artifacts.get("batch_id") or "result", download_dir=download_dir, ) ``` ```python task_root = ( Path(download_dir).expanduser().resolve() if download_dir else (TEMP_ROOT / "tasks" / task_id).resolve() ) task_root.mkdir(parents=True, exist_ok=True) zip_path = task_root / "result.zip" extracted_dir = task_root / "extracted" extracted_dir.mkdir(parents=True, exist_ok=True) ``` ### Technical Analysis The `task_id` or `batch_id` is extracted from remote API JSON and passed directly to `_download_and_extract()`. When no explicit `download_dir` is supplied, that remote value becomes a filesystem path component. Calling `resolve()` normalizes traversal components but does not ensure that the result remains beneath `TEMP_ROOT / "tasks"`. For example, identifiers containing `../` can escape the intended directory. An absolute identifier may also override preceding path components under `pathlib` path-composition semantics. After resolving the attacker-influenced path, the code creates directories, writes `result.zip`, creates an `extracted` directory, and extracts archive content there. The operation is constrained only by the filesystem permissions of the executing process. ### Attack Path 1. The skill communicates with a malicious or compromised API endpoint. 2. The API returns a crafted task or batch identifier containing traversal components, such as `../../attacker-target`. 3. The identifier is stored in `artifacts["task_id"]` or `artifacts["batch_id"]`. 4. Once processing completes, the identifier is passed to `_download_and_extract()`. 5. `(TEMP_ROOT / "tasks" / task_id). ...[truncated 958 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use remote task identifiers as directory names. Generate a local random identifier and store the remote identifier only as metadata. 2. If remote identifiers must be retained, enforce a strict allowlist such as `^[A-Za-z0-9_-]{1,128}$`. 3. Reject identifiers containing path separators, drive prefixes, `.` or `..` components, null bytes, or absolute paths. 4. Resolve both the intended root and candidate destination, then verify with `Path.is_relative_to()` that the candidate remains under the root. 5. Fail closed before creating directories or files when containment validation fails. 6. Use restrictive permissions for artifact directories and avoid following pre-existing symbolic links. 7. Consider opening output files with exclusive-creation semantics where overwriting is unnecessary. 8. Add tests covering traversal strings, absolute Unix paths, Windows drive paths, UNC paths, mixed separators, and symlink-based escape attempts. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
raw}")
    return value


def _missing_api_token_message() -> str:
    if os.name == "nt":
        current_session = '$env:MINERU_API_TOKEN="YOUR_MINERU_TOKEN"'
        persist = 'setx MINERU_API_TOKEN "YOUR_MINERU_TOKEN"'
        reopen = "After setx, restart Codex/Cursor or open a new terminal."
    else:
        current_session = 'export MINERU_API_TOKEN="YOUR_MINERU_TOKEN"'
        persist = 'echo \'export MINERU_API_TOKEN="YOUR_MINERU_TOKEN"\' >> ~/.bashrc'
        reopen = "Open a new shell after updating your profile."

    return "\n".join(
        [
            "MINERU_API_TOKEN not configured for API mode.",
            "Set it in your environment before using --mode api.",
            f"Docs: {DOCS_URL}",
            f"One-shot command: {current_session}",
            f"Persist for future sessions: {persist}",
            reopen,
            "If you want local parsing instead, use --mode local with a configured MinerU runtime.",
        ]
    )


def get_config() -> Config:
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill clearly instructs use of shell execution, environment variables, file reads/writes, and network access, but it does not declare an explicit tool scope such as permissions or allowed-tools. That increases the chance an agent will invoke powerful capabilities without clear policy boundaries, making misuse, overreach, or accidental data exposure more likely.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill promotes hosted API mode for local files but does not prominently warn that using --mode api with --file-path uploads the user's local document to an external MinerU service. In a document-processing skill, this is especially sensitive because PDFs and images often contain confidential business, legal, financial, or personal information.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The constant sets the default local parsing language to "ch", and later local configuration falls back to that value when the user does not explicitly choose a language. This imposes a specific locale by default without offering opt-in at the policy level or documenting a region-specific justification in the file.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if config.device_mode:
        command.extend(["-d", config.device_mode])

    completed = subprocess.run(
        command,
        capture_output=True,
        text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not curl_bin:
        raise RuntimeError("curl is not available for MinerU archive download fallback.")

    result = subprocess.run(
        [
            curl_bin,
            "-L",
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
In API mode, the function uploads the full local document to a remote MinerU service without any explicit in-code consent gate, warning, or privacy disclosure at the point of transfer. Because this skill handles potentially sensitive PDFs and images, silent exfiltration to a third-party service can violate user expectations, policy, or data-handling requirements.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The configuration section suggests using setx to persist the API token but does not warn that this stores credentials on the system for future sessions and may broaden exposure to other processes or users with access to the account. While not a direct secret leak by itself, it normalizes insecure credential handling and can lead to accidental retention of sensitive tokens.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The code automatically extracts the downloaded ZIP archive into a task directory, creating files and directories on the local filesystem. Although this behavior is part of result handling, this file provides no user-facing disclosure that API parsing will write and unpack artifacts locally.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This code writes the JSON result to a filesystem path by default whenever --stdout is not used, including creating parent directories and saving the file. Although the CLI help documents the output options, there is no explicit user-facing warning at execution time that parsed document data will be persisted to a temp or user-specified location.

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding
The dependency specifier `httpx>=0.27,<1` does not pin to a specific patched version, so builds may resolve to different releases over time and the manifest does not demonstrate that vulnerable versions are excluded. In a skill that performs API communication and local-file upload, relying on an unverifiable HTTP client version weakens supply-chain assurance and could expose the runtime to known library flaws if an affected release is installed.

Static analysis

No suspicious patterns detected.