Back to skill

Security audit

ClawHub Studio

Security checks for vulnerabilities and agentic risk

Overview

Review recommended: the studio has a coherent local authoring purpose, but its local API can be entered without credentials and can perform filesystem writes, Python test execution, and real ClawHub publishing.

Install only in a trusted single-user local environment, do not expose the server port, and avoid running it with elevated privileges. Treat self-tests as arbitrary Python code, review skill contents before publishing, keep dry-run enabled unless you explicitly intend to publish, and prefer a fixed version that validates slugs and filenames and adds real authorization or a startup secret.

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

T09 · Insecure Skill Coding Practices

Error
Location
studio/server.py:135
Finding

Unvalidated skill identifiers and manifest names permit workspace path traversal

Content
View full analysis
= 3 else None con = self._con() try: if len(parts) == 2: # POST /api/skills -> create skill b = self._body() try: sid = db.create_skill(con, b["slug"], b["name"]) except Exception: return _json(self, 409, {"error": "exists or invalid"}) return _json(self, 201, {"id": sid, "slug": b["slug"]}) if len(parts) == 3: # GET one skill (handled in GET); POST here is invalid return _json(self, 405, {"error": "method not allowed"}) if len(parts) == 4 and parts[3] == "versions": # add version b = self._body() try: vid = db.add_version(con, slug, b["version"], json.dumps(b.get("manifest", {}))) # scaffold real skill files on disk so test/publish have something to run workspace.scaffold(data_dir, slug, b.get("manifest", {})) ``` ```python # studio/workspace.py:42-63 def skill_dir(data_dir: str, slug: str) -> str: return os.path.join(data_dir, "skills", slug) def _tool_name(manifest: dict, slug: str) -> str: base = (manifest.get("name") or slug).lower().replace(" ", "_").replace("-", "_") return f"{base}.py" def scaffold(data_dir: str, slug: str, manifest: dict) -> str: """Create/update the skill folder from a manifest. Returns the folder path.""" d = skill_dir(data_dir, slug) os.makedirs(d, exist_ok=True) body = f"# {manifest.get('name', slug)}\n\n{manifest.get('description', '')}\n" with open(os.path.join(d, "SKILL.md"), "w", encoding="utf-8") as f: f.write(skills.manifest_to_skill_md(manifest, body=body)) tool = _tool_name(manifest, slug) with open(os.path.join(d, tool) ...[truncated 3009 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
studio/server.py:127
Finding

Credential-free login grants full privileged API access to any localhost caller

Content
View full analysis
= 3 else None ``` ### Technical Analysis `POST /api/login` mints a valid 12-hour HMAC bearer token without requiring a password, bootstrap secret, operating-system identity, prior session, or other proof of authorization. Consequently, possession of local network access is treated as sufficient authorization. The bearer token controls operations that are security-sensitive, including: - Creating filesystem-backed skill workspaces. - Executing Python self-tests as subprocesses. - Invoking the locally installed and already authenticated `clawhub` publishing client. - Modifying application database state. Binding to `127.0.0.1` reduces remote exposure but does not establish an authentication boundary. Any local process or other local user capable of connecting to the port can request a token. The server also does not validate the `Host` or `Origin` headers, leaving the design more exposed to localhost-oriented browser attacks such as DNS rebinding if an attacker can satisfy the relevant browser and DNS conditions. ### Attack Path 1. An untrusted local process or local user discovers or guesses the Studio port. 2. The attacker sends `POST /api/login` without credentials. 3. The server returns a valid signed bea ...[truncated 1253 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
93% confidence
Finding

The module-level documentation states the API is bearer-token authenticated, but the implementation mints tokens without authentication. This mismatch can cause operators and integrators to overtrust the service boundary and expose the studio assuming auth is meaningful when it is not.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The login endpoint issues a bearer token to any caller without validating credentials, so authentication is effectively disabled. Any local or proxied client that can reach the server can obtain a valid token and then access protected skill, test, and publish operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This is a markdown file, so missing-warning checks apply to documented behaviors that could affect user data or system integrity. The publish section explains how to disable dry-run and perform an actual publish, but it does not explicitly warn that doing so will make a real external change to the target account or recommend caution before use.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises and likely implements powerful capabilities including shell, filesystem, environment access, and network use, but the manifest shown declares no explicit tool scope or permissions boundaries. For an agent skill, this creates an under-specified trust boundary: a user or host system may invoke the skill without understanding that it can read/write files, access secrets from the environment, execute commands, or publish data externally.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The description promotes 'one-click ClawHub publishing' and 'publish-to-ClawHub' but does not clearly warn that publishing may transmit skill contents or metadata to an external service. In a local-first tool handling skill code, manifests, test outputs, and possibly auth/session material, lack of disclosure can mislead users into exfiltrating proprietary or sensitive data during publishing.

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 · studio/publish.py (reported line 33)May include surrounding context.

python
if dry_run:
        return {"published": False, "rc": 0, "output": "dry-run (skipped)", "command": " ".join(cmd)}
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        published = r.returncode == 0 and "success" in out.lower()
        return {"published": published, "rc": r.returncode, "output": out, "command": " ".join(cmd)}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code file exposes POST /api/login, which issues an authentication token immediately via auth.mint_token(data_dir). Although token issuance is central to the API, there is no confirmation prompt, log message, or explanatory comment warning that calling this endpoint creates active bearer credentials.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The /test endpoint runs testrunner.run_portfolio(folder), which is a code-execution/subprocess-style operation with potential system effects. In this file there is no confirmation, print/log statement, or warning comment disclosing that invoking the endpoint will execute tests against files on disk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The /publish endpoint calls publish.publish_skill(folder, dry_run=dry), which by its name and surrounding logic can perform a real publication when dry_run is false. The code lacks any confirmation prompt, logging, or warning comment disclosing that this action may transmit data externally or change release state.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The test runner is designed to execute Python files selected from a provided path/folder, which is effectively arbitrary code execution when pointed at an untrusted skill. That is a real security boundary issue, not just a generic testing feature, because a malicious skill can embed harmful behavior in its self-test and gain execution on the host machine.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
92% confidence
Finding

This function executes an arbitrary Python file path via subprocess with the current interpreter, which enables direct code execution of untrusted content if the path is user-controlled or sourced from an imported skill/project. In a studio that authors and tests agent skills, this is especially sensitive because 'self-test' code is still attacker-supplied code and can perform arbitrary local actions under the user's privileges.

Content

Scanner excerpt · studio/testrunner.py (reported line 21)May include surrounding context.

python
if not os.path.isfile(tool_path):
        return {"passed": False, "rc": -1, "output": f"no such file: {tool_path}"}
    try:
        r = subprocess.run(
            [sys.executable, tool_path, "self-test"],
            capture_output=True, text=True, timeout=timeout,
        )

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 · studio/testrunner.py (reported line 40)May include surrounding context.

python
harness = os.path.join(os.path.dirname(__file__), "..", "ci", "verify_product.py")
    harness = os.path.abspath(harness)
    if os.path.isfile(harness):
        r = subprocess.run(
            [sys.executable, harness, folder],
            capture_output=True, text=True, timeout=120,
        )

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 · tests/test_workspace.py (reported line 30)May include surrounding context.

python
folder = ws.scaffold(self.d, "demo", {"name": "Demo", "version": "0.1.0", "description": "d"})
        tool = [f for f in os.listdir(folder) if f.endswith(".py")][0]
        import subprocess
        r = subprocess.run([sys.executable, os.path.join(folder, tool), "self-test"],
                           capture_output=True, text=True)
        self.assertEqual(r.returncode, 0)
        self.assertIn("self-test: PASS", r.stdout)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code file performs filesystem modification via os.makedirs and initializes or migrates the SQLite database automatically, which are write operations affecting user data and system state. Although the module docstring mentions opening and migrating the database, there is no explicit user-facing disclosure, prompt, or warning at the point of action.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documentation states the module is 'Pure-ish' and only touches the given folder plus subprocess. In practice, run_portfolio constructs and executes ../ci/verify_product.py, accessing code outside the supplied folder, which contradicts the stated confinement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The document sets lang="en", which hard-codes English as the page language. Under the policy rule, forcing a specific language without user opt-in or documented justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.