Back to skill

Security audit

calwborate

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its collaboration-agent purpose, but it needs review because it stores a long-lived key locally, runs periodic remote actions, and has code paths that can exceed its stated storage and network boundaries.

Review before installing. Use this only if you trust the Clawborate publisher and backend with your Clawborate agent identity, projects, interests, conversations, and messages. Prefer running it in a dedicated account or tightly permissioned environment, protect CLAWBORATE_SKILL_HOME, and avoid enabling automatic interest acceptance or outbound automation unless you understand the Dashboard policy settings.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
runtime/storage.py:43
Finding

Long-Lived API Key Stored Without Explicit Access Restrictions

Content
View full analysis

Vulnerability Details

File Location: runtime/skill_runtime.py:59-71, runtime/storage.py:43-57
Vulnerability Type: Plaintext credential storage with ambient filesystem permissions
Risk Level: High

Vulnerable Code

python
class FileSecretStore:
    def __init__(self, path: Path):
        self.path = path

    def get_secret(self, name: str) -> str | None:
        data = load_json(self.path, {})
        value = data.get(name)
        return str(value) if value else None

    def set_secret(self, name: str, value: str) -> None:
        data = load_json(self.path, {})
        data[name] = value
        save_json(self.path, data)
python
def ensure(self) -> None:
    self.root.mkdir(parents=True, exist_ok=True)
    self.reports_dir.mkdir(parents=True, exist_ok=True)


def load_json(path: Path, default: Any) -> Any:
    if not path.exists():
        return default
    return json.loads(path.read_text(encoding="utf-8"))


def save_json(path: Path, payload: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")

Technical Analysis

The long-lived cm_sk_live_... API key is serialized directly into secrets.json. Neither the storage directory nor the secret file is assigned an explicit owner-only mode. The resulting access controls depend entirely on the process umask, inherited directory permissions, and whether the destination already exists.

Path.write_text() also follows symbolic links and does not provide an atomic, exclusive file creation guarantee. If the storage location is writable or manipulable by another local principal, that principal may be able to read the credential or redirect the write through a pre-positioned symbolic link.

This behavior exceeds the minimum safeguards expected for a file explicitly designated as a secret store.

At

...[truncated 1316 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create the Skill storage directory with mode 0700.
  2. Create secrets.json with mode 0600, independent of the ambient umask.
  3. Use an atomic write pattern:
    • Create a temporary file in the same directory with exclusive creation.
    • Set mode 0600 before writing sensitive data.
    • Flush and synchronize the file.
    • Atomically replace the destination.
  4. Reject symbolic links for both the storage directory and secret file. Where supported, use no-follow file-opening flags.
  5. Validate ownership and permissions when loading an existing secret file; refuse to proceed if it is group-readable, world-readable, or owned by another principal.
  6. Prefer an operating-system credential manager or platform-provided secret store over plaintext JSON.
  7. Avoid using the generic save_json() routine for credentials; implement a dedicated secret-writing function with mandatory security invariants.

T09 · Insecure Skill Coding Practices

Error
Location
runtime/config.py:21
Finding

Mutable Backend Configuration Can Redirect the Stored API Key

Content
View full analysis

Vulnerability Details

File Location: runtime/config.py:21-28, runtime/skill_runtime.py:112-134, runtime/client.py:46-70
Vulnerability Type: Credential exfiltration through an unrestricted service endpoint
Risk Level: High

Vulnerable Code

python
@classmethod
def from_dict(cls, data: dict[str, Any] | None) -> ClawborateConfig:
    payload = data or {}
    return cls(
        base_url=str(payload.get("base_url") or OFFICIAL_BASE_URL),
        anon_key=str(payload.get("anon_key") or OFFICIAL_ANON_KEY),
        worker_tick_seconds=int(payload.get("worker_tick_seconds") or DEFAULT_WORKER_TICK_SECONDS),
        agent_contact=payload.get("agent_contact"),
    )
python
def load_installed_context(home: Path | None = None) -> InstalledContext:
    layout = StorageLayout.from_root(home or default_skill_home())
    if not layout.config_path.exists():
        raise InstallError("not_installed", "Clawborate Skill is not installed yet.")
    config = ClawborateConfig.from_dict(load_json(layout.config_path, {}))
    secret_store = FileSecretStore(layout.secrets_path)
    agent_key = secret_store.get_secret(SECRET_NAME)
    if not agent_key:
        raise InstallError("missing_agent_key", "Clawborate agent key is missing. Reinstall or re-authorize the skill.")
    _sync_registration(layout, config)
    return InstalledContext(layout=layout, config=config, secret_store=secret_store, agent_key=agent_key)


def _build_client(
    context: InstalledContext,
    client_factory: Callable[[str, str, str], GatewayClient] | None = None,
) -> GatewayClient:
    if client_factory is not None:
        return client_factory(context.agent_key, context.config.base_url, context.config.anon_key)
    return GatewayClient(
        agent_key=context.agent_key,
        base_url=context.config.base_url,
        anon_key=context.config.anon_key,
    )
python
class GatewayClien
...[truncated 3464 chars]
Remediation
View remediation

Remediation Suggestions

  1. Because the Skill is declared as official-host-only, do not load base_url or anon_key from mutable persisted configuration.
  2. Always construct production clients with the compiled OFFICIAL_BASE_URL and OFFICIAL_ANON_KEY constants.
  3. If endpoint customization is required in a future self-hosted edition, separate that edition from the official-host-only package and require explicit user authorization.
  4. Validate customized endpoints using parsed URL components:
    • Require HTTPS.
    • Compare the normalized hostname against an explicit allowlist.
    • Restrict ports to approved values.
    • Reject user-information components, fragments, and malformed hosts.
  5. Disable redirects for credential-bearing RPC requests or validate every redirect target before forwarding credentials.
  6. Protect config.json with owner-only permissions and validate its ownership before loading it.
  7. Bind credentials to an expected service audience where supported, so a captured key cannot be used from an arbitrary endpoint.
  8. Add tests proving that modified configuration cannot redirect credential-bearing requests away from the official origin.

T09 · Insecure Skill Coding Practices

Error
Location
runtime/runner.py:297
Finding

Backend-Controlled Project Identifier Enables Report Path Traversal

Content
View full analysis

Vulnerability Details

File Location: runtime/runner.py:213-225, runtime/runner.py:297, runtime/runner.py:428
Vulnerability Type: Path traversal and arbitrary JSON file overwrite
Risk Level: High

Vulnerable Code

python
projects = (
    active_client.list_my_projects(limit=200)
    if client
    else list_my_projects(
        agent_key=agent_key,
        limit=200,
        base_url=base_url,
        anon_key=anon_key,
    )
)
projects = projects or []
python
for project in projects:
    project_id = project.get("id")
    project_name = project.get("project_name")
python
save_json(report_dir / f"{project_id}.json", project_summary)

The same unsafe report-path construction also occurs later in the loop:

python
save_json(report_dir / f"{project_id}.json", project_summary)
summary["projects"].append(project_summary)

The resulting path is written by:

python
def save_json(path: Path, payload: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")

Technical Analysis

project_id is obtained from a backend response and is treated as trusted when constructing a report filename. No validation rejects path separators, absolute paths, . components, or .. traversal components.

In pathlib, joining a directory with a relative value containing traversal components does not constrain the resulting path to that directory. For example, a project identifier such as ../../target produces a path equivalent to:

text
report_dir/../../target.json

The generic save_json() helper creates missing parent directories and then overwrites the resolved target. It does not verify that the final path remains beneath report_dir, and it does not reject symbolic links.

Therefore, a compromised backend—or an attacker- ...[truncated 1884 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not use externally supplied identifiers directly as filenames.
  2. Derive report filenames from a cryptographic hash or a safe encoding of the project identifier.
  3. If identifiers must remain visible, enforce a strict allowlist such as ASCII letters, digits, underscores, and hyphens.
  4. Reject identifiers containing:
    • / or \
    • ..
    • Absolute-path prefixes
    • Drive letters or UNC path forms
    • Null bytes or control characters
  5. Resolve and validate the destination before writing:
    python
    root = report_dir.resolve()
    candidate = (root / safe_filename).resolve()
    if not candidate.is_relative_to(root):
        raise ValueError("Report path escapes report directory")
    
  6. Reject symbolic links in every path component and use no-follow file-opening behavior where available.
  7. Write atomically with exclusive temporary-file creation and safe permissions.
  8. Validate backend response schemas and require project IDs to conform to the expected UUID or identifier format before any filesystem use.
  9. Add regression tests using traversal values, absolute paths, Windows path forms, and symlink destinations.
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This is another instance of the same core issue: the skill description implies broad implemented capabilities that are not shown in the analyzed file. In the context of a skill that stores an API key and interacts with a remote backend, inaccurate claims increase the risk of operators trusting secret handling, policy checks, or remote actions that may not exist.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This is another instance of the same core issue: the skill description implies broad implemented capabilities that are not shown in the analyzed file. In the context of a skill that stores an API key and interacts with a remote backend, inaccurate claims increase the risk of operators trusting secret handling, policy checks, or remote actions that may not exist.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This is another instance of the same core issue: the skill description implies broad implemented capabilities that are not shown in the analyzed file. In the context of a skill that stores an API key and interacts with a remote backend, inaccurate claims increase the risk of operators trusting secret handling, policy checks, or remote actions that may not exist.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This is another instance of the same core issue: the skill description implies broad implemented capabilities that are not shown in the analyzed file. In the context of a skill that stores an API key and interacts with a remote backend, inaccurate claims increase the risk of operators trusting secret handling, policy checks, or remote actions that may not exist.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This is another instance of the same core issue: the skill description implies broad implemented capabilities that are not shown in the analyzed file. In the context of a skill that stores an API key and interacts with a remote backend, inaccurate claims increase the risk of operators trusting secret handling, policy checks, or remote actions that may not exist.

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

md
---
name: clawborate-skill
description: Install and operate the official Clawborate runtime for OpenClaw agents. Use this skill when you need to validate a Clawborate agent key, manage projects, inspect market opportunities, work with interests and conversations, run market and message patrols, check message compliance, handle incoming interests, or fetch Clawborate reports without manually wiring .env files or cron jobs.
version: 0.2.3
homepage: https://sunday-openclaw.github.io/clawborate/
repository: https://github.com/Sunday-Openclaw/clawborate

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The skill explicitly stores sensitive material in a local secrets.json file, and the metadata states the agent key is persisted in the skill's private storage directory. Storing long-lived API credentials on disk is dangerous if file permissions are weak, backups are exposed, or another tool/skill can read that directory.

Content

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

md
Files written there:
- `config.json`
- `secrets.json`
- `state.json`
- `health.json`
- `registration.json`

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · bundle_manifest.json (reported line 47)May include surrounding context.

json
return cls(
            root=root,
            config_path=root / "config.json",
            secrets_path=root / "secrets.json",
            state_path=root / "state.json",
            health_path=root / "health.json",
            reports_dir=reports_dir,

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · runtime/storage.py (reported line 36)May include surrounding context.

python
return cls(
            root=root,
            config_path=root / "config.json",
            secrets_path=root / "secrets.json",
            state_path=root / "state.json",
            health_path=root / "health.json",
            reports_dir=reports_dir,

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
78% confidence
Finding

The skill advertises network, file read/write, and environment-related behavior in prose but does not declare an explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity where a host agent may grant broader capabilities than users expect, increasing the chance of unintended file access, secret handling, or outbound requests.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The invocation description is extremely broad and encourages use across many workflows without clear trigger constraints or guardrails. Overbroad routing can cause the skill to be invoked in contexts involving secrets, outbound messaging, or project actions more often than necessary, increasing attack surface and the chance of misuse.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code sends agent_key plus arbitrary payload data to a remote RPC endpoint via requests.post, but there is no confirmation prompt, print/log disclosure, or explanatory docstring/comment warning that user or system data will be transmitted off-host. The transmitted payload can include project content, contact details, and messages, making the network transfer safety-relevant under the missing-warning rule.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The delete_project method triggers a remote deletion action but contains no confirmation step, no user-facing log/print, and no explanatory comment or docstring warning that the action is destructive. This makes it easy for callers to invoke an irreversible operation without clear disclosure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The compliance engine embeds Chinese-language contact and commitment keywords alongside English ones, causing messages in those locales to be subject to policy enforcement without any visible language-selection mechanism or justification in this file. This is a natural-language policy concern because the skill imposes locale-specific moderation behavior by default rather than offering an explicit user choice.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code can automatically submit interests, start conversations, and update conversation state based solely on policy settings and model-produced report data, without any explicit local confirmation, warning, or audit gate at the point of action. In an agent skill that interfaces with external projects and communications, this creates a real risk of unintended outbound actions, spammy behavior, or policy/configuration abuse if the policy bundle, upstream data, or model decisions are wrong or manipulated.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The runner may automatically accept incoming interests whenever policy flags allow it, again without a local disclosure, interactive approval step, or secondary validation of the incoming item. That can cause the agent to commit to relationships or workflow transitions it did not intend, and an attacker or misconfigured policy could drive silent acceptance of unwanted or abusive requests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The delete_project action invokes client.delete_project(project_id) and immediately returns success, but this file provides no confirmation prompt, warning comment/docstring, or user-visible disclosure that a deletion is being performed. Because project deletion is a destructive and potentially irreversible operation, the code should include some form of explicit disclosure unless that warning is documented elsewhere in applicable markdown.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

The dependency specification allows any requests version from 2.31.0 up to but not including 3.0.0, which means installs are not reproducible and may resolve to a release with known security advisories depending on when and where the skill is deployed. In a skill that interacts with external services, agent keys, projects, conversations, and patrol/report APIs, an affected HTTP client library can increase risk of credential leakage, TLS/request handling issues, or other network-layer weaknesses.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The probe method issues a live HTTP request to the remote gateway with authorization headers, but the code provides no visible disclosure that invoking this check contacts an external service. Even though the payload is minimal, it still causes external network activity and should be clearly communicated.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

This code calls client.list_messages(conversation_id=conv_id) to retrieve conversation contents, which is a network/data-access operation involving user message data. In this file there is no confirmation prompt, user-facing log/print, or explicit warning comment/docstring disclosing that active conversations will be scanned and message contents retrieved.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/actions.py (reported line 39)May include surrounding context.

python
def require_arg(args: argparse.Namespace, name: str, cli_flag: str) -> str:
    value = getattr(args, name)
    if value in (None, ""):
        raise SystemExit(f"{args.action} requires {cli_flag}")
    return str(value)

Static analysis

No suspicious patterns detected.