Back to skill

Security audit

Multilingual Learning Sprint

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent language-learning and payment workflow, but its paid-service scripts can send payment credentials and learner data to an unvalidated URL and can access order files outside the intended folder.

Review this skill before installing if you plan to use real payments. Only run the payment scripts with a backend and order files you trust, avoid putting sensitive personal text in learner profiles, restrict or remove local .env.local overrides, and pin the Alipay npm package before production use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
scripts/service.py:45
Finding

Payment Credential and Learner Data Can Be Sent to an Untrusted URL

Content
View full analysis

Vulnerability Details

File Location: scripts/service.py:45-67
Vulnerability Type: Unvalidated outbound request destination and sensitive-data disclosure
Risk Level: High

Vulnerable Code:

python
def call_service(order_no: str, order_data: dict) -> dict:
    credential = order_data.get("payCredential")
    if not credential:
        raise RuntimeError("订单文件中缺少 payCredential 字段")

    kind = order_data.get("languageSprintKind") or "placement"
    payload = order_data.get("languageSprintRequest") or default_payload(order_data)
    resource_url = order_data.get("resource_url") or order_data.get("resourceUrl")
    if resource_url:
        url = resource_url
    else:
        base_url = os.environ.get("LANGUAGE_SPRINT_API_BASE_URL") or os.environ.get("APP_BASE_URL")
        if not base_url:
            base_url = DEFAULT_API_BASE_URL
        url = f"{base_url.rstrip('/')}/api/language-sprint/{kind}"

    request = urllib.request.Request(
        url,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "X-Language-Sprint-Order-No": order_no,
            "X-ClawTip-Pay-Credential": credential,
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(request) as response:
            return json.loads(response.read().decode("utf-8"))

Technical Analysis

The fulfillment destination is selected directly from the order file through resource_url or resourceUrl. The code does not validate the URL scheme, hostname, port, path, or resolved address before attaching the ClawTip payment credential and transmitting the learner payload.

The order file includes metadata received from the remote order service and is subsequently modified by an external payment CLI. Consequently, it is not a suitable source of authority for deciding where a sensitive cred ...[truncated 1956 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not accept a fulfillment URL from mutable order metadata. Construct the endpoint from a locally configured, trusted API origin.
  • If dynamic resource URLs are required, enforce an exact allowlist of approved HTTPS hostnames, ports, and path prefixes.
  • Parse URLs with a standards-compliant parser and reject:
    • Non-HTTPS schemes
    • Embedded usernames or passwords
    • Loopback, private, link-local, multicast, and unspecified IP addresses
    • Unexpected ports
    • Hostnames that resolve to prohibited address ranges
  • Revalidate resolved addresses when connecting to reduce DNS-rebinding exposure.
  • Disable redirects for credential-bearing requests or validate every redirect target before forwarding sensitive headers.
  • Bind the expected fulfillment endpoint to signed or cryptographically authenticated order metadata.
  • Minimize the learner payload and obtain explicit user consent before transmitting free-text interests or sample answers.
  • Use a short-lived, order-scoped credential that cannot authorize unrelated operations.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/file_utils.py:13
Finding

Unvalidated Order Identifiers Permit Filesystem Path Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/file_utils.py:13-26
Vulnerability Type: Path traversal and arbitrary JSON file access
Risk Level: Medium

Vulnerable Code:

python
def load_order(indicator: str, order_no: str) -> dict:
    json_path = os.path.join(get_orders_base_dir(indicator), f"{order_no}.json")
    if not os.path.isfile(json_path):
        raise RuntimeError(f"订单文件不存在: {json_path}")
    with open(json_path, "r", encoding="utf-8") as f:
        return json.load(f)


def save_order(indicator: str, order_no: str, order_data: dict) -> str:
    base_dir = get_orders_base_dir(indicator)
    os.makedirs(base_dir, exist_ok=True)
    json_path = os.path.join(base_dir, f"{order_no}.json")
    with open(json_path, "w", encoding="utf-8") as f:
        json.dump(order_data, f, ensure_ascii=False, indent=2)
    return json_path

The remote value reaches the file operation through scripts/create_order.py:96-107:

python
order_no = response.get("orderNo")
product = response.get("product") or {}
amount = product.get("amountFen") or (order or {}).get("amount")
if not order or not order_no or not amount:
    raise RuntimeError("API response missing clawtip.order, orderNo, or product.amountFen")
order["question"] = args.question
order["languageSprintKind"] = args.kind
order["languageSprintRequest"] = request_payload
save_order(indicator, order_no, order)

Technical Analysis

order_no is incorporated into a filesystem path without validation. Values containing traversal components such as ../, platform-specific separators, or an absolute path can cause the resolved path to leave the intended OpenClaw orders directory.

During order creation, order_no originates from a remote API response. During fulfillment, it is accepted as a command-line argument and passed to load_order. The indicator can also be supplied through the CLAWTIP_INDICATOR environme ...[truncated 1852 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate order_no before any filesystem use with a conservative allowlist, for example ^[A-Za-z0-9_-]{1,128}$.
  • Apply equivalent validation to the environment-controlled indicator.
  • Resolve both the base directory and candidate file path, then verify that the candidate remains beneath the base directory using Path.resolve() and Path.relative_to().
  • Reject absolute paths, path separators, . and .. components, control characters, and platform-specific alternate separators.
  • Create files atomically with exclusive semantics where replacement is not expected.
  • Use restrictive permissions for order directories and files because they may contain payment credentials and personal data.
  • Authenticate remote order responses and validate all returned fields before writing them locally.
  • Consider mapping externally supplied order numbers to locally generated opaque filenames rather than using external identifiers directly as path components.

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/service.py:17
Finding

Environment Loader Reads Configuration Outside the Project Boundary

Content
View full analysis

Vulnerability Details

File Location: scripts/service.py:17-28
Vulnerability Type: Excessive filesystem access and configuration injection
Risk Level: Low

Vulnerable Code:

python
def load_env_file() -> None:
    candidates = [Path.cwd() / ".env.local", Path(__file__).resolve().parents[3] / ".env.local"]
    for path in candidates:
        if not path.is_file():
            continue
        for raw_line in path.read_text(encoding="utf-8").splitlines():
            line = raw_line.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            key, value = line.split("=", 1)
            os.environ.setdefault(key.strip(), value.strip().strip("\"'"))

The same implementation appears in scripts/create_order.py:16-27.

Technical Analysis

The loader reads .env.local from the current working directory and from Path(__file__).resolve().parents[3]. The latter is not tied to the project root. In the audited artifact layout, it resolves outside the project directory. In other installation layouts, it can point to another broad ancestor location.

Every assignment found in such a file is imported into the process without an allowlist. Several environment variables influence security-sensitive behavior, including:

  • APP_BASE_URL
  • LANGUAGE_SPRINT_API_BASE_URL
  • CLAWTIP_SKILL_SLUG
  • CLAWTIP_INDICATOR

A file outside the Skill's configuration boundary can therefore redirect network requests or alter where order files are stored and loaded. Reading arbitrary ancestor configuration is not required for language tutoring or payment fulfillment and exceeds the minimum filesystem scope needed by the Skill.

Attack Path

  1. Another process or user places a .env.local file in the current working directory or the fixed ancestor selected by parents[3].
  2. The file defines a security-sensitive variable such as APP_BASE_URL or ...[truncated 892 chars]
Remediation
View remediation

Remediation Suggestions

  • Load configuration only from a documented project-local path or from a path explicitly supplied by the user.
  • Determine the project root directly from the package layout rather than using a fixed parents[3] traversal.
  • Resolve the configuration path and verify that it remains within the approved project or user-specific configuration directory.
  • Import only an explicit allowlist of required variables instead of every assignment in the file.
  • Prefer environment variables supplied by the invoking process for production deployments.
  • Reject configuration files with unsafe ownership or permissions where supported.
  • Log which configuration file is used, without logging secret values.
  • Apply the same correction to both scripts/service.py and scripts/create_order.py.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (20)

Tainted flow: 'request' from os.environ.get (line 58, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The request target is derived from environment-controlled configuration via LANGUAGE_SPRINT_API_BASE_URL or APP_BASE_URL and then used for an outbound POST containing user learning-profile data. If an attacker can influence the environment or .env.local contents, they can redirect requests to an arbitrary server for data exfiltration or SSRF-style network access; the skill context increases concern because the payload contains user-supplied profile content and order metadata.

Content

Scanner excerpt · scripts/create_order.py (reported line 66)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(request) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.HTTPError as error:
        response_text = error.read().decode("utf-8")

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is a benign language-learning coach, but the operational content includes creating local order files, invoking payment tooling, reading payment credentials, and fulfilling paid requests via backend scripts. This hidden behavioral expansion is dangerous because users or agent frameworks may invoke the skill for tutoring while silently enabling filesystem and payment-adjacent actions that were not transparently declared.

Content

No source excerpt is available for this finding.

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/create_order.py (reported line 17)May include surrounding context.

python
def load_env_file() -> None:
    candidates = [Path.cwd() / ".env.local", Path(__file__).resolve().parents[3] / ".env.local"]
    for path in candidates:
        if not path.is_file():
            continue

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/service.py (reported line 17)May include surrounding context.

python
def load_env_file() -> None:
    candidates = [Path.cwd() / ".env.local", Path(__file__).resolve().parents[3] / ".env.local"]
    for path in candidates:
        if not path.is_file():
            continue

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code trusts resource_url/resourceUrl from order data and uses it as the POST destination while attaching the order number and X-ClawTip-Pay-Credential header. That enables SSRF and credential exfiltration to any attacker-controlled endpoint, which is especially dangerous because this skill processes paid orders and transmits payment-linked secrets.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill text instructs use of local scripts, local order files, and external payment/network tooling, but it declares no explicit tool scope or permissions. That mismatch can cause an agent runtime to expose filesystem, environment, or network capabilities without clear least-privilege boundaries, increasing the chance of unintended file access or external calls during normal use.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description says to use the skill whenever a user wants to assess language ability, choose content, build a crash-course plan, practice any core language skill, run quizzes, schedule reviews, or expose payment flows. This is a very wide trigger scope with no explicit boundaries or negative examples, increasing the chance of unintended invocation for generic education or coaching requests.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
87% confidence
Finding

The skill recommends running npx -y @alipay/alipay-aipay@latest install, which pulls and executes the latest package version at runtime. This creates a supply-chain risk because a compromised or breaking upstream release could execute unreviewed code in the user's environment.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding

The document instructs users to execute an unpinned package via npx ...@latest, which creates a supply-chain risk because whatever version is current at execution time will be fetched and run. If the package is compromised, typo-swapped, or a bad release is published, operators integrating payments could execute attacker-controlled code in a sensitive environment.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · references/alipay-aipay.md (reported line 45)May include surrounding context.

md
The endpoint should:

1. Check whether the request includes a valid `Payment-Proof` header.
2. If missing, create a local order, sign a payment request, return HTTP 402, and set `Payment-Needed`.
3. If present, parse `Payment-Proof`, call `alipay.aipay.agent.payment.verify`, and confirm the proof is active.
4. Verify local order, amount, resource id, and idempotency before generating or releasing the resource.
5. Generate the lesson, quiz, or plan.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The example payload hard-codes currency as CNY and uses China-specific merchant identifiers and payment fields, while the document presents the integration as general guidance for monetizing the skill. Because the file does not clearly limit this section to China-only deployments or offer an alternative locale/payment path, it creates a natural-language locale policy issue.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script transmits user-provided language-learning profile data such as goals, interests, native language, and sample answers to a remote service without any user-facing disclosure or consent prompt. In this skill context, that data may include sensitive personal preferences or free-form text, so silent transmission creates a privacy/security issue even if the endpoint is intended.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The argument parser defaults --native-language to "Chinese", which means the skill assumes a specific language setting unless the user overrides it. This is a natural-language policy concern because it forces a locale-related choice without explicit user opt-in or justification.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest presents this skill primarily as an adaptive multilingual learning coach, while this file exclusively implements local persistence for order records under an 'orders' directory. Although the manifest mentions Alipay and JD ClawTip payment exposure, this code performs payment/order storage behavior rather than any language-learning operations, creating a meaningful description-to-behavior mismatch within the skill's implementation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The raised error message uses Chinese text only, which imposes a specific language on users regardless of their locale or preference. This is a natural-language policy issue because the file provides no choice, fallback, or documented justification for the language constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The default payload forces targetLanguage to English and nativeLanguage to Chinese when no explicit request is provided. This imposes a specific language/locale assumption in natural-language behavior without presenting a user choice or documenting a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code sends user-provided order data to a remote service and includes the sensitive payCredential value in an HTTP header. There is no confirmation prompt, user-facing log, or inline warning/comment disclosing that external transmission of potentially sensitive data will occur.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

This skill is described as a language-learning coach with payment/order support, and the network order creation behavior fits that purpose. However, explicitly searching for and reading local .env.local files is an additional capability not justified by the manifest description itself; it is a developer-oriented configuration access feature rather than learner- or payment-facing functionality.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The printed error message 订单创建失败 is user-facing text in Chinese with no language selection or fallback. This violates the language-choice policy because the script presents output in a fixed language without offering user opt-in or documenting a justified locale constraint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This code creates directories and writes JSON order data to disk, which is a user-data-affecting operation. There is no confirmation prompt, log/print message, or inline documentation here disclosing that the function persists data under the user's home directory.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.