Back to skill

Security audit

123pan upload and share

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real 123pan upload helper, but it uses cloud credentials, optional rclone/WebDAV flows, and subprocess/network behavior that are broader than its safest upload path.

Install only if you intend selected files to leave your machine and be stored on 123pan. Use a dedicated low-privilege 123pan token, set an isolated RCLONE_CONFIG, avoid running the WebDAV/rclone paths with unrelated secrets in the environment, pin dependencies in a virtual environment, and review generated links before sharing them.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload.py:85
Finding
Unvalidated API-Provided Upload Endpoint Receives Credentials and File Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/upload.py:85-94`, `scripts/upload.py:143-166`, `scripts/upload.py:328-339` **Vulnerability Type**: Improper validation of a remotely supplied network endpoint **Risk Level**: Medium ### Code Snippet ```python def get_upload_domain(): """Get upload domain from API.""" resp = requests.get( f"{API_BASE}/upload/v2/file/domain", headers=get_headers(), timeout=30 ) resp.raise_for_status() data = resp.json() if data.get("code") != 0: raise Exception(f"Failed to get upload domain: {data}") return data["data"][0] ``` ```python def upload_slice_v2(upload_server: str, preupload_id: str, slice_no: int, data: bytes, max_retries: int = 3) -> bool: """Upload a single slice using v2 API (multipart/form-data POST).""" import time url = f"{upload_server}/upload/v2/file/slice" slice_md5 = hashlib.md5(data).hexdigest() headers = get_upload_headers() for attempt in range(max_retries): try: files = { "slice": (f"slice_{slice_no}", data, "application/octet-stream") } form_data = { "preuploadID": preupload_id, "sliceNo": slice_no, "sliceMD5": slice_md5 } resp = requests.post( url, headers=headers, data=form_data, files=files, timeout=300 ) ``` ```python upload_domain = get_upload_domain() url = f"{upload_domain}/upload/v2/file/single/create" headers = get_upload_headers() data = { "parentFileID": folder_id, "filename": filename, "etag": etag, "size": file_size, "duplicate": 1 } with open(file_path, "rb") as f: files = {"file": (filename, f)} resp = requests.post(url, headers=headers, data=data, files=files, timeout=300) ``` ### Technical Analysis The upload host returned by `https: ...[truncated 1937 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse API-provided URLs with `urllib.parse.urlparse`. 2. Require the `https` scheme and reject embedded credentials, fragments, unexpected ports, and malformed hostnames. 3. Enforce an allowlist of documented 123pan upload domains or strictly validated domain suffixes. Domain checks must require either an exact match or a dot-delimited subdomain match to prevent suffix-confusion attacks. 4. Apply the same validation to both the single-upload domain and every entry in the chunked-upload `servers` list. 5. Do not send the API bearer token to storage hosts unless the official protocol explicitly requires it. Prefer short-lived, upload-scoped credentials or presigned URLs. 6. Disable cross-origin authorization forwarding during redirects, or reject redirects entirely for upload requests. 7. Add tests covering malicious values such as `http://`, attacker-owned HTTPS hosts, embedded credentials, deceptive suffixes, IP literals, and nonstandard ports. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/upload_rclone.py:16
Finding
Rclone Subprocess Inherits Unrelated Secrets and May Access Shared Cloud Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/upload_rclone.py:16-29`, `scripts/upload_rclone.py:70-79`, `scripts/upload_webdav_link.py:21-34`, `scripts/upload_webdav_link.py:80-89` **Vulnerability Type**: Excessive subprocess privileges and credential exposure **Risk Level**: Medium ### Code Snippet ```python RCLONE_BIN = os.environ.get("RCLONE_BIN") or subprocess.run( ["which", "rclone"], capture_output=True, text=True ).stdout.strip() or os.path.expanduser( "~/.openclaw/rclone-v1.73.2-linux-amd64/rclone" ) RCLONE_CONFIG = os.environ.get("RCLONE_CONFIG") def get_rclone_env(): """Get rclone environment variables with config isolation support.""" env = os.environ.copy() if RCLONE_CONFIG: env["RCLONE_CONFIG"] = RCLONE_CONFIG print(f"Using isolated rclone config: {RCLONE_CONFIG}") return env ``` ```python process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, env=get_rclone_env() ) ``` The same full-environment inheritance pattern appears in both rclone-based upload scripts. ### Technical Analysis The rclone child process receives a copy of the complete parent environment. This may include the 123pan API token, WebDAV password, and unrelated credentials such as cloud-provider keys, repository tokens, proxy credentials, or service secrets. Rclone only needs a narrowly scoped execution environment for this operation. In addition, `RCLONE_CONFIG` is optional. When it is absent, rclone may use its default shared configuration at `~/.config/rclone/rclone.conf`. That file can contain credentials for unrelated cloud services. The documentation warns users about this behavior, but does not enforce isolation. The executable is selected from user-controlled `RCLONE_BIN`, the process `PATH`, or a fixed path under the user's home directory. This behavior is useful for configuration, but it increases t ...[truncated 1522 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `os.environ.copy()` with a minimal environment allowlist containing only values required to run rclone, such as a controlled `PATH`, `HOME`, locale variables, and the dedicated `RCLONE_CONFIG`. 2. Explicitly remove `PAN123_ACCESS_TOKEN`, `PAN123_WEBDAV_PASS`, unrelated cloud credentials, and other secret-bearing variables from the child environment. 3. Require `RCLONE_CONFIG` instead of making it optional. Refuse execution when an isolated configuration is not supplied. 4. Validate that the isolated configuration has safe ownership and permissions, such as mode `0600`, and is not a symlink to an unexpected file. 5. Resolve the rclone executable to an absolute path and verify that it is a regular executable owned by an expected user or package manager. 6. Pin and verify the rclone distribution using an approved version and cryptographic checksum. 7. Consider avoiding an external subprocess by using a narrowly scoped WebDAV client where practical. 8. Apply identical hardening to both `upload_rclone.py` and `upload_webdav_link.py`. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/webdav_upload.py:15
Finding
Unpinned and Incompletely Declared Python Dependencies Permit Supply-Chain Substitution<![CDATA[ ## Vulnerability Details **File Location**: `README.md:40-44`, `scripts/webdav_upload.py:15-20` **Vulnerability Type**: Unpinned dependencies and import from a user-writable package directory **Risk Level**: Medium ### Code Snippet ```bash pip install requests ``` ```python # Add local site-packages for webdav3 local_site_packages = ( Path.home() / ".local" / "lib" / f"python{sys.version_info.major}.{sys.version_info.minor}" / "site-packages" ) if local_site_packages.exists(): sys.path.insert(0, str(local_site_packages)) from webdav3.client import Client from webdav3.exceptions import WebDavException ``` ### Technical Analysis The installation documentation directs users to install `requests` without a version constraint or integrity hash. The standalone WebDAV implementation additionally imports `webdav3`, but the project does not declare or pin that dependency in a lock file. The script explicitly prepends the current user's local `site-packages` directory to `sys.path`. As a result, a package located in that user-writable directory takes precedence over packages from other trusted installation locations. Python executes package initialization code at import time, before the upload workflow begins. This creates a supply-chain and local package-substitution risk. An unsafe package update, compromised package source, or malicious local `webdav3` package can execute with access to the same credentials and files as the Skill. ### Attack Path 1. An attacker compromises an unpinned dependency release or obtains the ability to place a malicious `webdav3` package in the user's local `site-packages` directory. 2. The user installs dependencies without a lock file or runs `scripts/webdav_upload.py` in the affected environment. 3. The script inserts the user-local package directory at the beginning of `sys.path`. 4. Python imports the attacker-controlled package and executes its initialization code. 5. The malicious package rea ...[truncated 606 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a dependency manifest and lock file that pins exact versions of `requests`, the intended WebDAV package, and all transitive dependencies. 2. Use cryptographic hashes, such as pip's `--require-hashes`, for reproducible and integrity-checked installations. 3. Document the exact package distribution that provides the `webdav3` module to avoid package-name ambiguity or typosquatting. 4. Remove the manual `sys.path.insert(0, ...)` modification and run the Skill in a dedicated virtual environment. 5. Install dependencies from an approved package index over TLS and consider using a controlled internal mirror. 6. Perform dependency vulnerability and provenance checks during release preparation. 7. Pin and verify non-Python dependencies, including rclone, using an approved version and checksum. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (33)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The top-level description emphasizes small-file single-step upload, but the skill also documents alternate WebDAV/rclone flows that require additional credentials and may access `~/.config/rclone/rclone.conf`. That mismatch can cause users or orchestrators to authorize the skill under incomplete assumptions, increasing the chance of unintended credential exposure or broader filesystem/config access.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The top-level description emphasizes small-file single-step upload, but the skill also documents alternate WebDAV/rclone flows that require additional credentials and may access `~/.config/rclone/rclone.conf`. That mismatch can cause users or orchestrators to authorize the skill under incomplete assumptions, increasing the chance of unintended credential exposure or broader filesystem/config access.

Credential Access

High
Category
Privilege Escalation
Content
{
        "name": "PAN123_ACCESS_TOKEN",
        "required": true,
        "description": "123pan API access token from https://www.123pan.com/dashboard/dev"
      },
      {
        "name": "PAN123_DIRECT_FOLDER_ID",
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
{
        "name": "PAN123_ACCESS_TOKEN",
        "required": true,
        "description": "123pan API access token from https://www.123pan.com/dashboard/dev"
      },
      {
        "name": "PAN123_DIRECT_FOLDER_ID",
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
],
    "suggested_isolation": {
      "rclone_config": "Create dedicated rclone config: rclone config --config ~/123pan-rclone.conf",
      "env_file": "Store credentials in .env file (add to .gitignore)"
    }
  },
  "inputs": {
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_rclone_env():
    """获取 rclone 环境变量,支持配置隔离"""
    env = os.environ.copy()
    if RCLONE_CONFIG:
        env["RCLONE_CONFIG"] = RCLONE_CONFIG
        print(f"Using isolated rclone config: {RCLONE_CONFIG}")
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.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def get_rclone_env():
    """获取 rclone 环境变量,支持配置隔离"""
    env = os.environ.copy()
    if RCLONE_CONFIG:
        env["RCLONE_CONFIG"] = RCLONE_CONFIG
        print(f"Using isolated rclone config: {RCLONE_CONFIG}")
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.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest says this skill uploads files to 123pan and generates shareable links, including short share links or direct download links. But this script stops after uploading and explicitly states that users must use the 123pan web interface to get share links, showing the implemented behavior is narrower than the described capability.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The README advertises a broader upload capability than the manifest context: it explicitly says the skill supports files over 1GB via chunked upload. The manifest, however, limits scope to small files under 1GB using single-step upload, so the documented behavior exceeds the stated skill purpose.

Session Persistence

Medium
Category
Rogue Agent
Content
openclaw skill install ./123pan-upload.skill

# 方法二:手动复制
mkdir -p ~/.openclaw/skills/123pan-upload
cp -r 123pan-upload/* ~/.openclaw/skills/123pan-upload/
```
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README instructs users to upload local files to a third-party cloud service without an explicit privacy or data-handling warning. In an agent/skill context, this can cause users to transmit sensitive local data off-host without fully understanding that the content leaves their environment and becomes subject to a third party's storage, logging, and sharing model.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
These lines describe a chunked upload process for files at or above 1GB, including create, get upload URL, upload parts, and complete operations. That documented capability is inconsistent with the manifest description, which says the skill works with small files under 1GB via single-step upload.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill documents use of environment variables, network access, and shell commands, but it does not declare any explicit tool scope or permissions boundary. That creates an execution-trust gap: an agent may invoke the skill with broader capabilities than users expect, including access to local environment secrets and arbitrary shell/network operations.

Tainted flow: 'filename' from requests.post (line 445, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
file_size = file_path.stat().st_size
    etag = get_md5(str(file_path))

    resp = requests.post(
        f"{API_BASE}/upload/v2/file/create",
        headers=get_headers(),
        json={
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'form_data' from requests.get (line 154, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
"sliceMD5": slice_md5
            }
            
            resp = requests.post(
                url,
                headers=headers,
                data=form_data,
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest description frames the skill as working with small files under 1GB via single-step upload. However, the code explicitly implements chunked uploads for files >=1GB and allows uploads up to a 10GB API limit, which is materially broader behavior than described.

Tainted flow: 'data' from requests.post (line 46, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
with open(file_path, "rb") as f:
        files = {"file": (filename, f)}
        resp = requests.post(url, headers=headers, data=data, files=files, timeout=300)

    resp.raise_for_status()
    result = resp.json()
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'file_id' from requests.post (line 444, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
def get_direct_link(file_id: int) -> str:
    """Get direct link for a file."""
    resp = requests.get(
        f"{API_BASE}/api/v1/direct-link/url",
        headers=get_headers(),
        params={"fileID": file_id},
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'filename' from requests.post (line 445, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
def create_share_link(file_id: int, filename: str) -> str:
    """Create share link for a file."""
    resp = requests.post(
        f"{API_BASE}/api/v1/share/create",
        headers=get_headers(),
        json={
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
from pathlib import Path

# 检测 rclone 路径:环境变量 > 系统 PATH > 默认本地路径
RCLONE_BIN = os.environ.get("RCLONE_BIN") or subprocess.run(["which", "rclone"], capture_output=True, text=True).stdout.strip() or os.path.expanduser("~/.openclaw/rclone-v1.73.2-linux-amd64/rclone")

# 支持隔离的 rclone 配置(避免读取 ~/.config/rclone/rclone.conf)
RCLONE_CONFIG = os.environ.get("RCLONE_CONFIG")
Confidence
84% confidence
Finding
The script allows the executable path to be controlled by the RCLONE_BIN environment variable, and otherwise resolves rclone from PATH using which. In an agent or multi-tenant execution environment, a malicious or tainted environment could cause execution of an attacker-controlled binary, leading to arbitrary code execution under the agent's privileges.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    
    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
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
]
    
    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'cmd' from os.environ.get (line 122, credential/environment) → subprocess.Popen (code execution)

Medium
Category
Data Flow
Content
]
    
    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
Confidence
88% confidence
Finding
The command executed by Popen inherits its first element from RCLONE_BIN, which is sourced from environment or PATH resolution. If an attacker can influence the environment, they can replace rclone with an arbitrary executable and gain code execution when uploads are performed.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Get remote file info using rclone ls
        cmd = [RCLONE_BIN, 'lsjson', remote_file]
        result = subprocess.run(cmd, capture_output=True, text=True, env=get_rclone_env())
        
        if result.returncode == 0:
            import json
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'cmd' from os.environ.get (line 122, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
# Get remote file info using rclone ls
        cmd = [RCLONE_BIN, 'lsjson', remote_file]
        result = subprocess.run(cmd, capture_output=True, text=True, env=get_rclone_env())
        
        if result.returncode == 0:
            import json
Confidence
88% confidence
Finding
This verification command also executes the binary selected through environment/PATH-controlled resolution, so the same binary-hijacking risk applies. Because the code runs after upload as part of normal workflow, a poisoned environment could reliably trigger attacker-controlled code.

Static analysis

No suspicious patterns detected.