Back to skill

Security audit

OpenMarlin

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent OpenMarlin integration, but it handles API keys, billing actions, and persistent state with several under-scoped security issues that merit Review before installation.

Install only if you are comfortable giving this skill an OpenMarlin API key and allowing it to submit tasks, inspect billing data, create top-up sessions, and store local OpenClaw state. Before use, keep OPENMARLIN_SERVER_URL on https://api.openmarlin.ai or a trusted HTTPS API origin, avoid custom http endpoints except trusted loopback development, treat any printed API key as exposed, and avoid sensitive prompt text until prompt retention and agent-id path validation are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openclaw_skill_config.py:131
Finding

Bearer Credentials and User Content Can Be Transmitted over Plaintext HTTP

Content
View full analysis
str: server_url = raw.strip().rstrip("/") if not server_url: raise SystemExit(build_server_url_setup_message()) parsed = urllib.parse.urlparse(server_url) if parsed.scheme in {"http", "https"} and parsed.netloc.lower() == "openmarlin.ai": raise SystemExit( build_server_url_setup_message( resolved_value=server_url, reason=( "OPENMARLIN_SERVER_URL points at the OpenMarlin website frontend. " f"Use the API origin instead: {DEFAULT_SERVER_URL}" ), ) ) return server_url ``` Authenticated requests subsequently use the accepted URL: ```python status, payload, response_headers = request( url=f"{server_url}/v1/executions", method="POST", headers={ "Authorization": f"Bearer {api_key}", }, payload=body, ) ``` Billing requests use the same URL validation and bearer credential: ```python api_key, api_key_source = resolve_api_key_or_exit( args.api_key, args.profile_id, args.agent_id, ) auth_headers = {"Authorization": f"Bearer {api_key}"} status, payload = fetch_authoritative_balance(server_url, auth_headers) ``` ### Technical Analysis `require_server_url()` accepts both HTTPS and plaintext HTTP origins. It only rejects the browser-facing `openmarlin.ai` hostname; it does not require TLS for remote hosts, validate that the value is a bare origin, or reject URL user information, queries, fragments, and unexpected paths. The validated value is later used for authenticated execution, task, model, balance, us ...[truncated 1738 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/openclaw_platform_auth.py:17
Finding

Unvalidated Agent Identifier Allows OpenClaw State-Directory Traversal

Content
View full analysis
Path: override = os.environ.get("OPENCLAW_STATE_DIR", "").strip() if override: return Path(override).expanduser() return Path.home() / ".openclaw" def resolve_agent_dir(agent_id: str = DEFAULT_AGENT_ID) -> Path: return resolve_openclaw_state_dir() / "agents" / agent_id / "agent" def resolve_auth_store_path(agent_id: str = DEFAULT_AGENT_ID) -> Path: return resolve_agent_dir(agent_id) / "auth-profiles.json" ``` The resulting path is used without containment validation: ```python def ensure_auth_store( agent_id: str = DEFAULT_AGENT_ID, ) -> tuple[Path, dict[str, Any]]: agent_dir = resolve_agent_dir(agent_id) agent_dir.mkdir(parents=True, exist_ok=True) try: agent_dir.chmod(0o700) except OSError: pass auth_path = resolve_auth_store_path(agent_id) if not auth_path.exists(): store: dict[str, Any] = {"version": 1, "profiles": {}} save_auth_store(auth_path, store) return auth_path, store with auth_path.open("r", encoding="utf-8") as handle: raw = handle.read().strip() ``` The same resolver is used for persistent task and billing files: ```python def resolve_task_state_path( agent_id: str = DEFAULT_AGENT_ID, ) -> Path: return resolve_agent_dir(agent_id) / "task-state.json" ``` ```python def resolve_billing_state_path( agent_id: str = DEFAULT_AGENT_ID, ) -> Path: return resolve_agent_dir(agent_id) / "billing-state.json" ``` ### Technical Analysis The command-line `--agent-id` value is inserted directly into a filesystem path. No validation rejects absolute paths, path ...[truncated 2000 chars]
Remediation
View remediation
Path: if not AGENT_ID_PATTERN.fullmatch(agent_id): raise ValueError("Invalid OpenClaw agent ID.") base = (resolve_openclaw_state_dir() / "agents").resolve() destination = (base / agent_id / "agent").resolve() if destination != base and base not in destination.parents: raise ValueError("Agent state path escapes the OpenClaw state directory.") return destination ``` Where supported, also avoid following untrusted symlinks when opening or replacing state files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/openclaw_task_state.py:145
Finding

Complete User Video Prompts Are Persisted Without a Retention Limit

Content
View full analysis
dict[str, Any]: state_path, state = ensure_task_state(agent_id) submissions = _submissions_bucket(state) now = format_iso_now() entry = { "server_url": server_url.rstrip("/"), "api_key_fingerprint": api_key_fingerprint, "request_fingerprint": request_fingerprint, "idempotency_key": idempotency_key, "kind": kind, "status": status, "prompt": prompt, "recorded_at": now, "last_seen_at": now, } if task_id: entry["task_id"] = task_id submissions[ _submission_key( server_url=server_url, api_key_fingerprint=api_key_fingerprint, request_fingerprint=request_fingerprint, ) ] = entry save_task_state(state_path, state) return {"task_state_path": str(state_path), "submission": entry} ``` The caller passes the complete prompt: ```python record_task_submission( server_url=server_url, api_key_fingerprint=api_key_fingerprint, request_fingerprint=request_fingerprint, idempotency_key=idempotency_key, task_id=None, kind=str(body.get("kind") or ""), status="submitting", prompt=str(body.get("input", {}).get("prompt") or "").strip(), agent_id=args.agent_id, ) ``` ### Technical Analysis The task deduplication feature already calculates a SHA-256 request fingerprint over the normalized task body. That fingerprint is sufficient to identify matching recent requ ...[truncated 1774 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/registration_session.py:412
Finding

Newly Issued API Keys Are Printed in Plaintext by Default

Content
View full analysis
None: if session is not None: print( f"Session ID: " f"{session.get('registration_session_id', '')}" ) completion = session.get("completion") if isinstance(completion, dict): print(f"Account ID: {completion.get('account_id', '')}") workspace = completion.get("workspace") if isinstance(workspace, dict): print( f"Workspace: {workspace.get('display_name', '')} " f"({workspace.get('slug', '')})" ) api_key = result.get("api_key") if not isinstance(api_key, dict): raise SystemExit( "API key bootstrap response is missing api_key metadata." ) print(f"Key ID: {api_key.get('key_id', '')}") print(f"Workspace ID: {api_key.get('workspace_id', '')}") print(f"Status: {api_key.get('status', '')}") print(f"Label: {api_key.get('label', '')}") print(f"Created at: {api_key.get('created_at', '')}") print(f"Last used at: {api_key.get('last_used_at', '')}") print(f"Secret: {result.get('secret', '')}") print("Export:") print( f" export OPENMARLIN_PLATFORM_API_KEY=" f"'{result.get('secret', '')}'" ) ``` The output function is used even after successful storage: ```python if args.json: print(json.dumps(output, indent=2, sort_keys=True)) else: print_api_key_issue(result, session) if storage: print("Store ...[truncated 1958 chars]
Remediation
View remediation
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (21)

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

The persisted OpenClaw config path is:

text
skills.entries["openmarlin"].env

So OpenClaw can remember values such as:

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The supplied code is narrowly focused on billing and payment recovery operations: balance lookup, top-up creation/status watching, billing activity inspection, 402 recovery, and referral-link retrieval. That aligns with the 'billing flows' portion of the description, but the declared purpose also claims broader capabilities—answering questions, running tasks, and managing account setup—that are not represented in this code chunk. Additionally, the code includes referral/attribution retrieval and local state recording for billing sessions/snapshots, which are not mentioned in the description. Because the declared description materially overstates the primary capabilities shown by this code and omits at least one distinct implemented feature, this is a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared purpose presents a broad user-facing OpenMarlin integration for answering questions, executing tasks, and handling account setup/billing. The supplied code does none of those things. Its sole function is local credential persistence and retrieval for an OpenMarlin API key inside OpenClaw state directories. While credential handling could be a supporting detail for a larger integration, this chunk by itself materially differs from the declared primary purpose and introduces a specific capability—local auth profile storage—that is not reflected in the description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a broad end-user capability set: interacting with OpenMarlin to answer questions, execute tasks, and handle account setup/billing flows. This code chunk does not implement any of those product behaviors. Instead, it is a configuration helper module that reads persisted config from specific filesystem paths, checks process environment variables, constructs instructional setup/error text, validates that a server URL is an API origin rather than the website frontend, and sends a GET request to /openapi.json to test connectivity. While config handling could be considered a supporting detail for a larger OpenMarlin integration, this chunk's actual purpose is narrowly focused on configuration and connectivity validation, which is materially different from the declared primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The code does support using OpenMarlin to run executions and manage some task workflows, which partially matches 'answer questions' and 'run tasks.' However, the declared description also claims account setup and billing flows, and this code chunk does not implement account registration/bootstrap, account setup, payment, or billing management actions. It only handles authenticated API requests plus error messaging for insufficient balance and local billing snapshot recording. Additionally, the code includes materially specific capabilities not reflected in the description, such as model listing, task polling/status, idempotent task deduplication, and local state recording. Because the declared purpose overstates account/billing functionality and understates the actual operational scope, this is a mismatch.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
If you install it manually, copy both `SKILL.md` and the sibling `scripts/`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
If you install it manually, copy both `SKILL.md` and the sibling `scripts/`

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

Install into that default location:

bash
mkdir -p "$HOME/.openclaw/workspace/skills/openmarlin"
rsync -a --delete \
  --exclude '.git' \
  /path/to/openmarlin-skill-directory/ \

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · README.md (reported line 78)May include surrounding context.

After install, the main entrypoint should be:

text
~/.openclaw/workspace/skills/openmarlin/SKILL.md

The helper scripts remain available relative to that installed skill directory:

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · README.md (reported line 174)May include surrounding context.

md
7. Send your first routed execution with `python3 scripts/platform_request.py executions --body-json '{"instruction":"hello"}'`.
8. For long-running jobs such as video generation, default to `python3 scripts/platform_request.py tasks-submit --watch ...` so submission and polling happen in one flow.

Do not ask users to paste platform API keys into chat during setup. Registration
should issue and store the key through the bootstrap flow above.

If you plan to force a specific `provider_id` and also pass an exact `model`

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises broad operational behavior, including running Python helper scripts, network access, credential bootstrap/storage, and billing workflows, but does not declare any explicit tool scope or allowed-tools restrictions. This creates an avoidable over-privilege and transparency problem: a caller or host may not realize the skill can invoke shell, access environment/configuration, and make outbound API requests, increasing the blast radius if the skill is misrouted, modified, or abused.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
- store the key in the default OpenClaw auth profile when `--store` is used
- avoid echoing raw secrets unless the active command explicitly returns them
- when reporting success, show where the key was stored or loaded from
- never ask the user to paste a platform API key into chat as the normal
  registration path
- if `openmarlin.ai` appears blocked by browser or Cloudflare protection,
  verify that helpers are targeting `https://api.openmarlin.ai` before

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The explain-402 flow supports --auto-recover, which can immediately create a top-up session based on a parsed insufficient-balance response without an explicit confirmation step from the user at execution time. In an agent-driven context, this increases the risk of unintended financial actions, especially if a manipulated or misunderstood 402 payload is supplied and the agent proceeds automatically.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest describes the skill as answering questions, running tasks, and managing OpenMarlin account setup and billing flows. This file primarily implements billing operations, but the referral-link command fetches referral code, invite link, and attribution summary from /v1/referrals/me, which is a marketing/referral capability rather than an obvious billing or account-setup function.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Calling /v1/referrals/me to retrieve referral code, invite link, and attribution counts introduces a referral-program capability that is not necessary to answer questions, run tasks, or manage billing/top-up flows. This is a distinct product-growth function and is not an obvious implementation detail of billing support.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code persists the full task prompt to a local JSON state file, which can expose sensitive user content such as secrets, personal data, billing details, or operational instructions if the host is multi-user, backed up, synced, or later inspected. Although the file permissions are tightened to 0600/0700 and the API key itself is only fingerprinted, storing raw prompts still creates unnecessary at-rest data exposure for an account-management and task-execution skill where prompts may contain sensitive information.

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

python
system = platform.system().lower()
    try:
        if system == "darwin":
            completed = subprocess.run(
                ["open", browser_url],
                check=False,
                capture_output=True,

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

python
os.startfile(browser_url)  # type: ignore[attr-defined]
            return True, "Opened the authorization page in your default browser."
        else:
            completed = subprocess.run(
                ["xdg-open", browser_url],
                check=False,
                capture_output=True,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script prints the issued API key secret directly to stdout and even emits a ready-to-export shell command containing the secret. In CLI and agent contexts, stdout is commonly captured in logs, transcripts, terminal history, orchestration traces, or chat artifacts, which can unintentionally disclose a long-lived credential to other users or systems.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The manifest emphasizes account setup and billing flows such as balance handling and top-up recovery. The activity command pulls recent usage events and ledger entries from /v1/usage-events and /v1/ledger, which is more of an account analytics/audit feature than a direct billing-flow helper described in the manifest.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

When --store is used, the code persists the issued API key via upsert_platform_api_key, which performs a file write of sensitive credentials. Although the CLI flag help mentions storage, there is no execution-time notice, confirmation, or nearby warning in the code path that a local auth file will be modified with secret material.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.