Back to skill

Security audit

Unlimited-OCR PDF & Document Parsing

Security checks for vulnerabilities and agentic risk

Overview

This OCR skill does what it says, but it can send documents and OCR credentials to configurable network endpoints without strong safeguards.

Install only if you trust the publisher and the OCR service you configure. Avoid endpoint override environment variables except in controlled testing, do not process confidential documents unless you are allowed to send them to the selected provider, prefer a locked dependency environment, and store outputs in a private directory.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/client.py:61
Finding

Configurable endpoints can redirect credentials and document contents to arbitrary hosts

Content
View full analysis
str: try: parsed = urlparse(raw) except ValueError as exc: raise SkillError("CONFIG_ERROR", f"{name} must be a valid URL") from exc if parsed.username or parsed.password: raise SkillError("CONFIG_ERROR", f"{name} must not contain embedded credentials") is_loopback_http = allow_loopback_http and parsed.scheme == "http" and parsed.hostname in LOOPBACK_HOSTS if parsed.scheme != "https" and not is_loopback_http: raise SkillError("CONFIG_ERROR", f"{name} must use HTTPS (loopback HTTP is allowed)") if not parsed.hostname: raise SkillError("CONFIG_ERROR", f"{name} must include a hostname") return raw.rstrip("/") ``` ```python return cls( api_key=api_key, secret_key=secret_key, access_token=access_token, oauth_url=safe_endpoint(env("UNLIMITED_OCR_OAUTH_URL", BAIDU_OAUTH_URL), "UNLIMITED_OCR_OAUTH_URL"), submit_url=safe_endpoint(env("UNLIMITED_OCR_SUBMIT_URL", BAIDU_SUBMIT_URL), "UNLIMITED_OCR_SUBMIT_URL"), query_url=safe_endpoint(env("UNLIMITED_OCR_QUERY_URL", BAIDU_QUERY_URL), "UNLIMITED_OCR_QUERY_URL"), timeout_seconds=positive_float(timeout_seconds, "timeout_seconds", 7200), poll_interval_seconds=positive_float(poll_interval_seconds, "poll_interval_seconds", 60), ) ``` ```python def _access_token(client: httpx.Client, settings: BaiduSettings) -> str: if settings.access_token: return settings.access_token response = client.post(settings.oauth_url, params={ "grant_type": "client_credentials", "client_id": settings.api_key, "client_secret": settings.secret_key, }) value = _json_re ...[truncated 2634 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/client.py:183
Finding

Response-controlled artifact URLs permit server-side requests to loopback services

Content
View full analysis
bytes: url = safe_endpoint(raw_url, label) with client.stream("GET", url) as response: if response.status_code < 200 or response.status_code >= 300: raise SkillError("HTTP_ERROR", f"{label} download failed with HTTP {response.status_code}") chunks: list[bytes] = [] size = 0 for chunk in response.iter_bytes(): size += len(chunk) if size > MAX_ARTIFACT_BYTES: raise SkillError("RESPONSE_ERROR", f"{label} exceeds the 64 MiB download limit") chunks.append(chunk) return b"".join(chunks) ``` ```python if isinstance(markdown_url, str) and markdown_url: artifacts["markdown_url"] = markdown_url text = _download(client, markdown_url, "markdown_url").decode("utf-8", errors="replace") if isinstance(parse_result_url, str) and parse_result_url: artifacts["parse_result_url"] = parse_result_url raw = _download(client, parse_result_url, "parse_result_url").decode("utf-8", errors="replace") try: parsed_result = json.loads(raw) except json.JSONDecodeError: parsed_result = raw ``` ### Technical Analysis The `markdown_url` and `parse_result_url` values are supplied by the configured OCR service. They are passed to `_download()`, which validates them with `safe_endpoint()`. That validator permits HTTP requests to `127.0.0.1`, `localhost`, and `::1`. Consequently, a compromised, malicious, or incorrectly trusted OCR endpoint can instruct the client to issue GET requests to services listening on the victim's loopback interface. The response is decoded and included in the returned envelope, allowing accessible local content to be ...[truncated 1417 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/unlimited_ocr_caller.py:3
Finding

Runtime dependencies are dynamically resolved without a lockfile or integrity hashes

Content
View full analysis
=3.9" # dependencies = [ # "httpx>=0.27,<1", # "pymupdf>=1.24,<2", # ] # /// ``` The documented execution path dynamically invokes the script through `uv`: ```bash uv run scripts/unlimited_ocr_caller.py --file-path "/absolute/path/document.pdf" --pretty ``` The smoke test similarly declares ranged dependencies: ```python # /// script # requires-python = ">=3.9" # dependencies = [ # "httpx>=0.27,<1", # "pymupdf>=1.24,<2", # "pillow>=10,<13", # ] # /// ``` ### Technical Analysis The project supplies broad version ranges through inline script metadata but does not include a reviewed lockfile or package hashes. Running the documented `uv run` command may therefore resolve and download dependency versions that were not present during the audit. This creates a mutable supply-chain boundary: the effective code executed by the Skill can change when a new matching package version is published. No evidence was found that the currently named packages are malicious; the risk is the absence of reproducible, integrity-verified dependency resolution. Imported dependencies execute with the same privileges as the OCR process and can access documents, environment variables, output files, and network resources. ### Attack Path 1. A dependency publisher account, package registry, mirror, or package release is compromised. 2. A malicious version is published within one of the accepted version ranges. 3. A user runs the documented `uv run` command in an environment where that version is selected. 4. `uv` downloads and installs the changed package. 5. The package executes during import or normal library use. 6. Malicious dependency code accesses OCR credentials, local do ...[truncated 620 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/unlimited_ocr_caller.py:23
Finding

Sensitive OCR results are written with inherited default filesystem permissions

Content
View full analysis
Path: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") return Path(tempfile.gettempdir()) / "unlimited-ocr" / "results" / f"result-{timestamp}-{uuid.uuid4().hex[:8]}.json" ``` ```python if args.markdown_output and result.get("ok"): markdown_path = Path(args.markdown_output).expanduser().resolve() markdown_path.parent.mkdir(parents=True, exist_ok=True) markdown_path.write_text(str(result.get("text", "")), encoding="utf-8") if args.stdout: print(rendered) else: output_path = Path(args.output).expanduser().resolve() if args.output else default_output_path().resolve() output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(rendered, encoding="utf-8") print(f"Result saved to: {output_path}", file=sys.stderr) ``` ### Technical Analysis The default output contains the complete OCR text, provider-specific responses, and artifact URLs. It is stored beneath the shared operating-system temporary directory. Neither the result directory nor the output file is assigned an explicit restrictive permission mode. `Path.mkdir()` and `Path.write_text()` rely on process defaults and the current umask. On systems or service environments with permissive defaults, the resulting files or directories may be readable by other local users. The same issue applies to a user-selected Markdown output path. Randomized filenames make guessing more difficult but do not replace access controls. The code also does not define retention or cleanup for default result files. ### Attack Path 1. The Skill processes a sensitive document. 2. The process runs with a permissive umask or in an environment where the temporary result directory is access ...[truncated 872 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 27)May include surrounding context.

md
description: Baidu OCR application Secret Key.
      - name: UNLIMITED_OCR_ACCESS_TOKEN
        required: false
        description: Existing Baidu OAuth access token alternative.
      - name: UNLIMITED_OCR_LOCAL_BASE_URL
        required: false
        description: HTTPS or loopback HTTP URL of the local model service.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill explicitly requires environment variables, reads local files, writes output files, and performs network requests, but it does not declare any explicit tool scope or permissions boundary. This creates a capability/expectation gap where an agent may invoke a networked, file-handling skill without clear authorization constraints, increasing the risk of unintended data disclosure or misuse of local documents and credentials.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This CLI sends document content to an OCR backend via parse_document, which may be a third-party cloud provider or a local OpenAI-compatible service, but the script gives no explicit user-facing warning at the point of execution that file contents may leave the local system. In a document-processing skill, users may pass sensitive PDFs, scans, invoices, or reports, so silent transmission increases the risk of unintentional disclosure of confidential data.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.potential_exfiltration

Python code base64-encodes a local file and sends it over the network.

Critical
Code
suspicious.potential_exfiltration
Location
scripts/client.py:210