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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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)

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/extract_pdf.py (reported line 17)May include surrounding context.

python
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:

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.

Content

No source excerpt is available for this finding.