Back to skill

Security audit

Web Clipper

Security checks for vulnerabilities and agentic risk

Overview

This is a plausible web-clipping skill, but its automatic execution and path handling create review-worthy local execution and file-write risks.

Review before installing. Use it only for public URLs you are comfortable sending to Jina Reader, avoid automatic execution from vague chat context, and do not pass untrusted `--folder`, `--vault`, or URL values until shell-safe execution and vault path containment are added.

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

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:4
Finding

Shell Command Injection Through User-Controlled Command Arguments

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:4 and SKILL.md:57-61
Vulnerability Type: Shell command injection
Risk Level: High

Vulnerable Code

text
description: Save any web page as an Obsidian-compatible Markdown clipping. Triggered by "save <URL>" or "保存这个". Uses Jina Reader API for clean content extraction. Supports custom tags, subfolders, and vault paths. Saves to ~/.openclaw/obsidian-cache/clippings/ by default. TRIGGER "save <URL>" / "保存这个" / any URL with "save" or "clip" -> ALWAYS exec: python3 ~/.openclaw/skills/web-clipper/scripts/save_web_page.py --url "URL" -> Confirm the saved filename to user. NEVER use memory.
bash
python3 ~/.openclaw/skills/web-clipper/scripts/save_web_page.py \
  --url "https://example.com/article" \
  --folder "clippings/tech" \
  --tags "ai,tools" \
  --vault "/path/to/your/obsidian/vault"

Technical Analysis

The Skill directs the agent to construct a command by interpolating user-controlled URL, folder, tag, and vault values into a textual shell command. Quoting a value with double quotes does not neutralize shell command substitution, and a quote character supplied by the user can terminate the quoted argument.

For example, if a shell processes a URL containing a payload such as:

text
https://example.com/"; id; #

direct substitution into the documented command can produce:

bash
python3 ~/.openclaw/skills/web-clipper/scripts/save_web_page.py --url "https://example.com/"; id; #"

The shell would execute id as a separate command. Command substitution constructs such as $() can also be evaluated inside double quotes.

The Python script uses argparse and does not itself invoke a shell. The vulnerable boundary is therefore the Skill instruction that encourages textual shell-command construction. Exploitability depends on the agent execution tool passing that command through a shell rather than ...[truncated 1069 chars]

Remediation
View remediation

Remediation Suggestions

  • Require a structured process-execution API that passes an argument array without invoking a shell:

    python
    [
        "python3",
        script_path,
        "--url",
        user_url,
        "--folder",
        user_folder,
        "--tags",
        user_tags,
        "--vault",
        user_vault,
    ]
    
  • Explicitly state in SKILL.md that the command must never be executed with shell=True, sh -c, bash -c, or an equivalent shell interpreter.

  • Do not ask the agent to create one textual command through direct interpolation.

  • Validate that URLs contain only expected URL syntax before execution.

  • Treat --folder, --tags, and --vault as untrusted data even when the URL is valid.

  • If a shell is unavoidable, use a proven platform-specific argument-quoting mechanism for every value and reject shell control characters. Structured non-shell execution remains the preferred fix.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/save_web_page.py:105
Finding

Path Traversal and Absolute-Path Injection Through the Folder Argument

Content
View full analysis

Vulnerability Details

File Location: scripts/save_web_page.py:105-106 and scripts/save_web_page.py:147
Vulnerability Type: Unrestricted filesystem path construction
Risk Level: Medium

Vulnerable Code

python
folder_path = Path(vault) / folder
folder_path.mkdir(parents=True, exist_ok=True)

The affected argument is accepted without validation:

python
parser.add_argument("--folder", default=DEFAULT_FOLDER, help="Subfolder within vault")

Technical Analysis

The --folder option is described as a subfolder within the selected vault, but the implementation does not enforce that boundary.

A relative value containing parent-directory components, such as ../../target, can escape the vault. In addition, Python's pathlib discards the preceding path when the right-hand operand is absolute, so an absolute folder value can replace the selected vault path entirely.

The resulting path is then created recursively and used as the destination for a Markdown file:

python
filepath.write_text(note, encoding="utf-8")

The filename is generated from the date and sanitized remote title, so an attacker cannot directly choose an arbitrary filename through --folder. Existing files with the generated name are also not overwritten because the code adds a counter suffix. Nevertheless, the attacker can cause new directories and files to be written outside the intended vault.

Attack Path

  1. An attacker causes the Skill to run with a folder such as ../../outside-vault or an absolute writable path.
  2. Path(vault) / folder produces a destination outside the configured vault.
  3. mkdir(parents=True, exist_ok=True) creates the external directory hierarchy.
  4. The remote page is retrieved and its attacker-influenced title and content are written as a Markdown file in that external location.
  5. Any software that monitors or processes files in the selected destination may subse ...[truncated 566 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject absolute values for --folder.

  • Resolve the vault and destination paths before creating directories.

  • Verify that the resolved destination is a descendant of the resolved vault:

    python
    vault_path = Path(vault).expanduser().resolve()
    
    folder_input = Path(folder)
    if folder_input.is_absolute():
        raise ValueError("--folder must be relative to the vault")
    
    folder_path = (vault_path / folder_input).resolve()
    
    try:
        folder_path.relative_to(vault_path)
    except ValueError:
        raise ValueError("--folder must remain inside the vault")
    
  • Perform the containment check before mkdir.

  • Consider rejecting .. path components at argument-validation time as an additional defense.

  • Where the threat model includes concurrent filesystem modification, use directory handles or equivalent platform facilities to reduce symlink race risks.

T08 · Insecure Dependencies

Note
Location
SKILL.md:28
Finding

Unpinned Third-Party Dependency Installation

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:28
Vulnerability Type: Unpinned dependency and non-reproducible installation
Risk Level: Low

Vulnerable Code

text
- `requests` library: `pip install requests`

Technical Analysis

The installation instruction retrieves whichever version of requests and its transitive dependencies satisfies the package index at installation time. No reviewed version, lock file, hash, or integrity constraint is provided.

The package name is legitimate, and the project does not direct users to an untrusted package index or exhibit dependency-confusion or typosquatting behavior. The risk is instead that installation is not reproducible and automatically trusts future releases and dependency resolution results.

Attack Path

  1. A user follows the documented pip install requests instruction.
  2. pip resolves the package and transitive dependencies available from its configured index at that time.
  3. A compromised, unexpectedly changed, or incompatible release is selected.
  4. Package installation or later import executes or loads code that was not part of the audited project snapshot.
  5. That code receives the privileges of the Python environment in which the Skill runs.

This path requires a compromised or unsafe package source, a compromised future release, or an incompatible dependency update; no such compromise was identified in the reviewed files.

Impact Assessment

A malicious dependency could execute code during installation or when imported, with the permissions of the installing user or Skill process. A non-malicious incompatible update could instead cause availability or correctness failures.

Because the referenced package is established and the audit found no suspicious source configuration, the present risk is low.

Remediation
View remediation

Remediation Suggestions

  • Add a reviewed requirements or lock file containing exact dependency versions.

  • Include package hashes and install with hash verification:

    bash
    python3 -m pip install --require-hashes -r requirements.txt
    
  • Pin and review transitive dependencies as well as the direct requests dependency.

  • Install dependencies inside a dedicated virtual environment rather than a shared or privileged Python environment.

  • Use a trusted package index explicitly and periodically update pins after security review and testing.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

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

md
import requests


# Only these keys are loaded from .env — no other secrets are touched
_ALLOWED_ENV_KEYS = {"JINA_API_KEY", "OPENCLAW_VAULT"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/save_web_page.py (reported line 23)May include surrounding context.

python
import requests


# Only these keys are loaded from .env — no other secrets are touched
_ALLOWED_ENV_KEYS = {"JINA_API_KEY", "OPENCLAW_VAULT"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/save_web_page.py (reported line 28)May include surrounding context.

python
import requests


# Only these keys are loaded from .env — no other secrets are touched
_ALLOWED_ENV_KEYS = {"JINA_API_KEY", "OPENCLAW_VAULT"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/save_web_page.py (reported line 31)May include surrounding context.

python
import requests


# Only these keys are loaded from .env — no other secrets are touched
_ALLOWED_ENV_KEYS = {"JINA_API_KEY", "OPENCLAW_VAULT"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/save_web_page.py (reported line 64)May include surrounding context.

python
import requests


# Only these keys are loaded from .env — no other secrets are touched
_ALLOWED_ENV_KEYS = {"JINA_API_KEY", "OPENCLAW_VAULT"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/save_web_page.py (reported line 33)May include surrounding context.

python
Only keys listed in _ALLOWED_ENV_KEYS are imported.
    All other secrets in .env are ignored.
    """
    env_path = Path.home() / ".openclaw" / ".env"
    if not env_path.exists():
        return
    with open(env_path) as f:

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger definition is excessively broad: it activates on any URL appearing with words like 'save' or 'clip' and instructs the agent to ALWAYS execute the script. This can cause unintended file writes and external network requests from ordinary conversation, especially because the skill performs side effects without a clear confirmation step.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill description does not clearly warn users that requested URLs are sent to the external Jina Reader API and that the resulting content is written to local disk. This creates a privacy and consent problem, because users may unknowingly transmit sensitive URLs or persist data locally when they only intended a read-only assistant action.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

The outbound POST to r.jina.ai transmits the requested URL off-host, which can expose sensitive browsing targets or internal resources if users provide such URLs. Although the code appears intended to clip web pages, the skill context makes this more dangerous because it operates on arbitrary user input and silently relies on a remote service for retrieval.

Content

Scanner excerpt · scripts/save_web_page.py (reported line 83)May include surrounding context.

python
if api_key:
        headers["Authorization"] = f"Bearer {api_key}"

    resp = requests.post("https://r.jina.ai/", headers=headers, json={"url": url}, timeout=60)
    resp.raise_for_status()
    data = resp.json()

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

The outbound POST to r.jina.ai transmits the requested URL off-host, which can expose sensitive browsing targets or internal resources if users provide such URLs. Although the code appears intended to clip web pages, the skill context makes this more dangerous because it operates on arbitrary user input and silently relies on a remote service for retrieval.

Content

Scanner excerpt · scripts/save_web_page.py (reported line 83)May include surrounding context.

python
if api_key:
        headers["Authorization"] = f"Bearer {api_key}"

    resp = requests.post("https://r.jina.ai/", headers=headers, json={"url": url}, timeout=60)
    resp.raise_for_status()
    data = resp.json()

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The module is presented as a utility to save a web page as an Obsidian Markdown clipping, but it additionally loads values from a local secrets file into the process environment. While one key is used for the API and another for the default vault path, accessing a hidden local .env file is a broader capability than the basic clipping purpose implies when no manifest is available to justify it.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.