Back to skill

Security audit

Resilient PDF

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it downloads arbitrary URLs and runs an unpinned third-party PDF converter at runtime, which deserves review before installation.

Install only if you are comfortable with local execution of a runtime-resolved PDF conversion package and with agents downloading URLs from their network context. Prefer trusted PDF URLs, workspace-local output paths, and a sandbox or pinned converter environment for sensitive documents or untrusted links.

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

T08 · Insecure Dependencies

Warning
Location
scripts/extract_pdf.py:84
Finding
Unpinned Third-Party Package Is Resolved and Executed at Runtime## Vulnerability Details **File Location**: `scripts/extract_pdf.py:84-85` **Vulnerability Type**: Runtime dependency resolution and supply-chain exposure **Risk Level**: Medium ### Technical Analysis The extractor invokes `uvx` with the unpinned package specification `markitdown[pdf]`: ```python def extract_with_markitdown(pdf_path: Path, output_path: Path, timeout: int) -> dict: uvx = detect_uvx() if not uvx: return { 'ok': False, 'method': 'markitdown', 'error': 'uvx not found', 'install_hint': "python3 -m pip install --user --break-system-packages uv", } ensure_parent(output_path) cmd = [uvx, '--from', 'markitdown[pdf]', 'markitdown', str(pdf_path), '-o', str(output_path)] try: proc = run(cmd, timeout=timeout) ``` Because no exact version, lock file, package hash, or controlled package index is specified, each invocation may resolve a different release and set of transitive dependencies. This makes the effective executable code mutable after the Skill has been reviewed. The dependency is executed with the same operating-system identity and permissions as the Skill. Although using `uvx` is necessary to support the declared extraction workflow, dynamically resolving an unconstrained package exceeds the minimum supply-chain trust required for that functionality. ### Attack Path 1. An attacker compromises a future release of `markitdown`, one of its PDF extras, or a transitive dependency available through the configured package index. 2. An operator invokes the PDF extraction workflow. 3. `uvx --from 'markitdown[pdf]'` resolves the affected package version. 4. Package installation or execution runs attacker-controlled code under the invoking user's account. 5. The malicious dependency can access files and resources available to that account, including the PDF being processed and workspace data. ### Impact ...[truncated 446 chars]
Remediation
## Remediation Suggestions - Pin `markitdown` and all relevant transitive dependencies to audited versions. - Use a lock file with cryptographic hashes and enforce hash verification during installation. - Resolve packages from a controlled, trusted index rather than implicitly trusting the runtime environment's configured indexes. - Prefer a prebuilt, reviewed virtual environment or container instead of resolving dependencies during every extraction. - Establish an explicit dependency-update process that includes security review and testing. - Run PDF conversion in a sandbox with restricted filesystem access, network access, process creation, and resource limits.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/extract_pdf.py:48
Finding
Unrestricted URL Fetching Enables Internal Network Access and Resource Exhaustion## Vulnerability Details **File Location**: `scripts/extract_pdf.py:48-52`, with user-controlled invocation at `scripts/extract_pdf.py:169-174` **Vulnerability Type**: Server-side request forgery and unbounded response handling **Risk Level**: Medium ### Technical Analysis The downloader accepts a caller-provided URL and passes it directly to `urllib.request.urlopen`. It does not restrict URL schemes or destination hosts, reject loopback/private/link-local addresses, validate redirect destinations, limit redirects, or impose a maximum response size: ```python def download_pdf(url: str, download_path: Path, timeout: int) -> dict: ensure_parent(download_path) req = urllib.request.Request(url, headers={'User-Agent': 'OpenClaw resilient-pdf/1.1.0'}) try: with urllib.request.urlopen(req, timeout=timeout) as response: content_type = response.headers.get('Content-Type', '') payload = response.read() except Exception as exc: return {'ok': False, 'error': f'download failed: {exc}', 'url': url} ``` The URL reaches this function directly from the command-line argument: ```python if args.url: download_name = derive_filename_from_url(args.url) download_target = Path(args.download_to).expanduser().resolve() if args.download_to else (output_path.parent / download_name) dl = download_pdf(args.url, download_target, args.timeout) if not dl.get('ok'): print(json.dumps(dl, indent=2) if args.json else dl.get('error', 'download failed')) return 1 ``` PDF validation occurs only after the complete response has been loaded into memory. Therefore, it does not prevent the initial request from reaching internal services and does not protect against oversized downloads. Redirects can also move a request from an apparently public URL to a prohibited internal destination unless each redirect target is independently validated. Network fetching i ...[truncated 1889 chars]
Remediation
## Remediation Suggestions - Permit only explicitly supported `http` and `https` URLs. - Reject embedded credentials and unsupported or ambiguous URL forms. - Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified addresses for both IPv4 and IPv6. - Revalidate DNS resolution at connection time to reduce DNS-rebinding exposure. - Disable automatic redirects or validate every redirect destination using the same scheme and address rules. - Set a strict redirect count and consider an allowlist of trusted hosts where operationally feasible. - Stream responses in bounded chunks instead of calling `response.read()` without a limit. - Enforce a maximum compressed/download size and a maximum saved-file size, aborting and deleting partial files when exceeded. - Apply connection and read timeouts separately and enforce an overall transfer deadline. - Run downloads and PDF conversion in a sandbox with restricted network access, filesystem scope, memory, CPU, and disk quotas. - Require explicit operator confirmation before accessing untrusted or non-allowlisted hosts.
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs the agent to perform file reads/writes, network downloads, and shell execution, but it does not declare any explicit tool scope such as permissions or allowed-tools. That gap weakens least-privilege controls and makes the skill harder to safely govern, especially because it fetches remote PDFs and runs a local helper that may invoke external tooling.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd: list[str], *, timeout: int = 600) -> subprocess.CompletedProcess:
    return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)


def ensure_parent(path: Path) -> None:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Rp1

Medium
Category
MCP Rug Pull
Confidence
87% confidence
Finding
The script invokes uvx with an unpinned package target, `markitdown[pdf]`, which can fetch and execute whatever version is current at runtime. In a skill designed to process potentially untrusted PDFs, this increases supply-chain risk substantially: a compromised upstream release or dependency could lead to arbitrary code execution on the host when extraction is attempted.

Static analysis

No suspicious patterns detected.