Back to skill

Security audit

Medo App Builder

Security checks for vulnerabilities and agentic risk

Overview

This skill matches its Medo app-building purpose, but needs review because it can send your Medo API key and project instructions to any configured API host and its docs suggest weak key storage.

Install only if you trust the Medo account and this publisher. Use a temporary or narrowly scoped Medo API key, avoid setting MEDO_BASE_URL or --base-url unless you fully trust the destination, do not store the key in the skill directory .env file, and review before publishing because publish makes the app publicly available.

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
scripts/medo_api.py:786
Finding

Bearer API Key and User Data Can Be Sent to an Arbitrary Endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/medo_api.py:35-43, scripts/medo_api.py:76-82, and scripts/medo_api.py:786-789
Vulnerability Type: Unrestricted authenticated API destination
Risk Level: High

Vulnerable Code

python
def auth_headers(api_key: str) -> dict:
    """Return HTTP headers with Bearer token authentication, JSON content type, and custom User-Agent."""
    return {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "User-Agent": f"medo-app-builder/{CLIENT}-{VERSION}",
    }
python
url = f"{base_url.rstrip('/')}/api/v1/app/list"
payload = {
    "name": name,
    "page": page,
    "size": size,
    "sort": {"direction": "desc", "property": "updatedAt"},
}
resp = requests.post(url, json=payload, headers=auth_headers(api_key), timeout=30)
python
parser.add_argument(
    "--base-url",
    default=os.environ.get("MEDO_BASE_URL", DEFAULT_BASE_URL),
    help=f"Platform host URL (default: {DEFAULT_BASE_URL})",
)

The same authenticated-request pattern is also used at lines 114, 236, 284, 371, 446, 535, 715, and 727.

Technical Analysis

The CLI accepts the API base URL from either the MEDO_BASE_URL environment variable or the --base-url command-line option. This value is used without scheme validation, hostname validation, or an allowlist. The CLI then attaches the Medo bearer token to requests made to the resulting URL.

Network access and bearer authentication are necessary for the Skill's declared app-building functionality. However, allowing unrestricted control over the destination of authenticated requests exceeds the minimum privilege required to communicate with the declared Medo service.

In addition to the API key, affected requests can disclose user prompts, app identifiers, conversation identifiers, release identifiers, and app-management operations. Chat requests can co ...[truncated 1646 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove --base-url and MEDO_BASE_URL support if custom endpoints are not required.
  2. If endpoint overrides are necessary for testing, validate the normalized URL before attaching credentials:
    • Require HTTPS.
    • Reject embedded user information.
    • Reject unexpected ports.
    • Permit only an explicit allowlist of trusted Medo hostnames.
    • Resolve and compare the final hostname rather than using substring or suffix checks that can be bypassed.
  3. Separate production and development credentials, and require explicit user confirmation before sending a credential to a non-production endpoint.
  4. Configure redirect handling so authorization headers are never forwarded to a different origin. Prefer disabling redirects for authenticated API calls unless they are explicitly required.
  5. Avoid accepting security-sensitive endpoint configuration from an ambient environment where untrusted launchers or inherited variables can alter it.
  6. Add tests confirming that HTTP URLs, lookalike domains, subdomain tricks, unexpected ports, and cross-origin redirects are rejected.
  7. Rotate any API key suspected of having been used while MEDO_BASE_URL or --base-url was controlled by an untrusted party.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:836
Finding

Documentation Recommends Plaintext API-Key Storage Without Restrictive Permissions

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:836-839
Vulnerability Type: Insecure credential storage guidance
Risk Level: Medium

Vulnerable Code

bash
# Or create a `.env` file in the skill directory:
echo "MEDO_API_KEY=sk_xxxxx" > ~/.openclaw/skills/medo-app-builder/.env

Technical Analysis

The documented command writes the bearer credential into a plaintext file. It does not set restrictive file permissions, verify the current umask, use a secret manager, or warn against backups and accidental repository inclusion.

File permissions therefore depend on the user's environment. The secret may be readable by unintended local principals or captured by backups, archives, diagnostics, synchronization tools, or accidental package publication.

The audited Python script reads MEDO_API_KEY from the process environment but does not itself load the documented .env file. As a result, this persistence mechanism is not required by the CLI implementation and may not solve the stated authentication problem unless an external component separately loads the file.

Attack Path

  1. A user follows the documented error-recovery instructions.
  2. The shell writes the real API key to the Skill directory as plaintext.
  3. The resulting permissions are determined by the current umask; no explicit owner-only mode is applied.
  4. Another local process or account reads the file, or the file is included in a backup, archive, synchronization operation, or copied Skill directory.
  5. The exposed bearer key is replayed against the Medo API.

Impact Assessment

Exposure grants the attacker the Medo API privileges associated with the key. Based on the operations implemented by this Skill, those privileges may include reading project metadata and conversation data, modifying or generating applications, and initiating production publication.

The scope is the authenticated Medo account and resources access ...[truncated 188 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the plaintext .env recommendation and direct users to the platform's supported secret-management facility.
  2. Prefer injecting MEDO_API_KEY into the process only for the duration of the command.
  3. If file-based storage is unavoidable:
    • Create the file with owner-only permissions, such as by setting umask 077 before creation.
    • Verify that the final mode is 0600.
    • Store it outside the Skill package and source tree.
    • Ensure it is excluded from version control, archives, logs, and diagnostic bundles.
    • Document secure rotation and deletion procedures.
  4. Do not document a .env workflow unless the Skill deliberately and securely supports loading it.
  5. Recommend rotating the token immediately if the file was exposed or distributed.

T08 · Insecure Dependencies

Note
Location
SKILL.md:858
Finding

Unpinned Global Dependency Downgrade Is Recommended to Suppress a Warning

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:858-866
Vulnerability Type: Unsafe dependency installation guidance
Risk Level: Low

Vulnerable Code

bash
pip3 install 'urllib3<2'

Technical Analysis

The documentation recommends installing any urllib3 release satisfying a broad version constraint. The package version and artifact hash are not pinned, no reviewed lock file is used, and no isolated virtual environment is required. Package resolution also depends on the user's configured Python package indexes.

This instruction is presented as an optional way to suppress NotOpenSSLWarning, rather than as a requirement for the Skill's core functionality. It can downgrade an existing environment and alter dependencies used by unrelated applications. The broad constraint may also resolve differently over time.

This is an insecure supply-chain practice, although the audited project does not itself retrieve or execute a remote payload beyond normal Python package installation initiated by the user.

Attack Path

  1. A user encounters the documented TLS compatibility warning.
  2. The user runs the recommended pip3 install command in the active or global Python environment.
  3. pip resolves a matching package from the user's configured index without a project-supplied hash or exact reviewed version.
  4. A compromised index, maliciously configured mirror, compromised package release, or vulnerable resolved version is installed.
  5. The installed package executes when this Skill or another Python application imports it.

Impact Assessment

The direct impact includes dependency downgrading, environment instability, and exposure to vulnerabilities present in the selected older release. If package resolution is compromised, installation can execute package-controlled build or installation behavior with the privileges of the user running pip.

The exact privilege scope depends on how the co ...[truncated 232 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the dependency-downgrade workaround if the warning does not affect functionality.
  2. Resolve TLS compatibility by using a supported Python build linked against a supported OpenSSL version.
  3. If a compatibility dependency is genuinely necessary:
    • Use a dedicated virtual environment.
    • Pin an exact reviewed version.
    • Pin package hashes in a requirements or lock file.
    • Use a trusted, explicitly configured package index.
    • Run vulnerability scanning against the selected version.
  4. Avoid modifying a system-wide or shared Python environment solely to suppress a warning.
  5. Document supported Python, OpenSSL, requests, and urllib3 versions instead of relying on an open-ended downgrade constraint.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

The skill explicitly instructs users to write MEDO_API_KEY into a plaintext .env file under the skill directory. Storing long-lived API credentials in a predictable local path materially increases the chance of credential theft via filesystem disclosure, accidental commits, backups, or other skills/tools reading local files.

Content

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

  1. Or create a .env file in the skill directory:

    bash
    echo "MEDO_API_KEY=sk_xxxxx" > ~/.openclaw/skills/medo-app-builder/.env
    

Context Leakage

High
Category
Data Exfiltration
Confidence
78% confidence
Finding

The get_context_id and conversation-history logic retrieves and exposes full trajectory data for an app, including historical conversation metadata and potentially command/file observation content, based only on possession of the API key and appId. In a multi-tenant or shared-token environment, this can leak prior prompts, file contents, and conversation identifiers beyond what a caller minimally needs, increasing the blast radius of token misuse.

Content

Scanner excerpt · scripts/medo_api.py (reported line 571)May include surrounding context.

python
app_id: str,
    fetch_timeout: int = 10,
) -> Optional[str]:
    """Extract the conversationId (contextId) for an existing app by reading its trajectory.

    Scans ALL trajectory events and returns the MOST RECENT (last) non-empty contextId
    found, checking two paths per event:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill invokes a Python CLI that requires environment-variable access and makes outbound API calls, but it declares no explicit tool scope or permission boundaries. In an agent ecosystem, this can cause the skill to run with broader capabilities than reviewers or orchestrators expect, increasing the chance of unintended secret exposure or unsanctioned network actions.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
71% confidence
Finding

The documented use of conversation-history --full can emit complete prior interaction content without truncation, which may include sensitive prompts, internal specs, identifiers, or secrets that were previously entered. In an agent setting, unbounded output also increases the risk of over-collection and accidental propagation of sensitive data into logs, model context, or downstream tools.

Content

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

Only show the last 3 turns

python scripts/medo_api.py conversation-history --app-id app-abc123xyz --limit 3

Show full content without truncation

python scripts/medo_api.py conversation-history --app-id app-abc123xyz --full

text

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The conversation-history examples show user and agent interactions in Chinese, and nearby command examples throughout the document use Chinese prompt text as the default interaction style. Because the skill does not state that language is optional or user-selected, this can be read as imposing a specific language/locale on users without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The primary chat usage examples for creating and modifying apps are written in Chinese, but the skill does not explain whether Chinese is required, preferred, or merely an example. That creates a natural-language policy concern because it appears to steer or require a specific language without user opt-in or justification.

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 · SKILL.md (reported line 679)May include surrounding context.

Complete Workflow Examples

Example 1: Create and Deploy a New App

bash
export MEDO_API_KEY="sk_xxxxx"

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 · SKILL.md (reported line 713)May include surrounding context.

Each chat call prints a JSON header line {"appId": "...", "conversationId": "..."}. The platform may return an updated conversationId in this response. Always capture it and use it for the next chat call, or the next round will create a brand-new app.

bash
export MEDO_API_KEY="sk_xxxxx"

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 · SKILL.md (reported line 760)May include surrounding context.

md
Used to:

* create a new application
* refine PRD specifications
* modify an existing generated application

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/medo_api.py (reported line 82)May include surrounding context.

python
"size": size,
        "sort": {"direction": "desc", "property": "updatedAt"},
    }
    resp = requests.post(url, json=payload, headers=auth_headers(api_key), timeout=30)
    result = raise_for_api_error(resp)
    data = result.get("data", {})
    # Accommodate both "items" and "list" response keys (API may vary)

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/medo_api.py (reported line 236)May include surrounding context.

python
"size": size,
        "sort": {"direction": "desc", "property": "updatedAt"},
    }
    resp = requests.post(url, json=payload, headers=auth_headers(api_key), timeout=30)
    result = raise_for_api_error(resp)
    data = result.get("data", {})
    # Accommodate both "items" and "list" response keys (API may vary)

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/medo_api.py (reported line 715)May include surrounding context.

python
"size": size,
        "sort": {"direction": "desc", "property": "updatedAt"},
    }
    resp = requests.post(url, json=payload, headers=auth_headers(api_key), timeout=30)
    result = raise_for_api_error(resp)
    data = result.get("data", {})
    # Accommodate both "items" and "list" response keys (API may vary)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The helper sets the chat language default to "zh", which means requests will be sent in Chinese unless the caller overrides code behavior. This is a natural-language policy concern because the file does not offer an end-user language choice or document a justified region-specific restriction.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/medo_api.py (reported line 284)May include surrounding context.

python
chat_url = f"{base_url.rstrip('/')}/api/v1/conversation/chat"
    payload = _build_chat_payload(text, context_id, app_id, query_mode, input_field_type)

    resp = requests.post(chat_url, json=payload, headers=auth_headers(api_key), timeout=60)
    resp.raise_for_status()
    chat_result = resp.json()
    _check_chat_response_for_error(chat_result)

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/medo_api.py (reported line 371)May include surrounding context.

python
chat_url = f"{base_url.rstrip('/')}/api/v1/conversation/chat"
    payload = _build_chat_payload(text, context_id, app_id, query_mode, input_field_type)

    resp = requests.post(chat_url, json=payload, headers=auth_headers(api_key), timeout=60)
    resp.raise_for_status()
    chat_result = resp.json()
    _check_chat_response_for_error(chat_result)

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documentation first states that project preview/editor URLs use https://www.medo.dev/projects/<app_id>, but later says the editor URL is https://www.medo.cn/projects/<app_id>. These two statements actively contradict each other, which can mislead agents about where application projects are actually accessed and shared.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.