Back to skill

Security audit

TeamClaw

Security checks for vulnerabilities and agentic risk

Overview

TeamClaw is a coherent multi-agent orchestration platform, but it grants broad automation and credential-bearing service access with weak scoping that should be reviewed before installation.

Install only if you are comfortable running a powerful local multi-agent service with code execution, persistent memory, stored API keys, and optional public access. Keep it bound to localhost unless you have reviewed the exposed routes, avoid exposing OASIS directly, do not use untrusted callback URLs or external expert API URLs, rotate tokens if exposure is suspected, and prefer running it in a separate low-privilege account or container.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
oasis/server.py:118
Finding

Unauthenticated callback URL leaks the privileged internal service token

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
oasis/engine.py:163
Finding

Attacker-controlled external expert URL can exfiltrate the OpenClaw API key

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/mcp_commander.py:256
Finding

Advertised command sandbox permits unrestricted host-level code execution

Content
View full analysis
str: """ 在用户的隔离工作目录中执行 Python 代码片段。 适用于数据计算、文本处理、简单脚本等场景. """ workspace = _user_workspace(username) # 将代码写入临时文件执行(比 -c 参数更可靠,支持多行和特殊字符) tmp_script = os.path.join(workspace, ".tmp_exec.py") try: with open(tmp_script, "w", encoding="utf-8") as f: f.write(code) proc = await asyncio.create_subprocess_exec( _python_cmd(), tmp_script, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=workspace, env=_sandbox_env(workspace, username), ) ``` ```python # Tools that need automatic username injection USER_INJECTED_TOOLS = { # File management tools "list_files", "read_file", "write_file", "append_file", "delete_file", # Command execution tools "run_command", "run_py ...[truncated 2841 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
oasis/server.py:531
Finding

OASIS APIs trust caller-supplied identities and permit path traversal

Content
View full analysis
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/setup_env.sh:13
Finding

Setup and startup execute mutable remote payloads without integrity verification

Content
View full analysis
/dev/null; then echo "✅ uv 已安装: $(uv --version)" else echo "📦 未检测到 uv,正在安装..." curl -LsSf https://astral.sh/uv/install.sh | sh # 加载 uv 到当前 shell export PATH="$HOME/.local/bin:$PATH" ``` ```python def download_bark_server(): """Download bark-server binary to bin/ directory.""" os_name, arch = detect_bark_platform() if not os_name: print("⚠️ 当前平台不支持自动下载 Bark Server,请手动安装") return False # Bark Server GitHub release URL (official repo: Finb/bark-server) url = f"https://github.com/Finb/bark-server/releases/latest/download/bark-server_{os_name}_{arch}" print(f"📥 正在下载 Bark Server ({os_name}/{arch})...") print(f" 来源: {url}") bin_dir = os.path.join(PROJECT_ROOT, "bin") os.makedirs(bin_dir, exist_ok=True) try: urllib.request.urlretrieve(url, BARK_SERVER_PATH) os.chmod( BARK_SERVER_PATH, os.stat(BARK_SERVER_PATH).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, ) print("✅ Bark Server 下载完成") return True ``` ```python def ensure_bark_server(): """Ensure bark-server binary is available, download if needed.""" if os.path.isfile(BARK_SERVER_PATH) and os.access(BARK_SERVER_PATH, os.X_OK): print(f"✅ 已找到 Bark Server: {BARK_SERVER_PATH}") return True print("⚠️ 未找到 Bark Server,开始自动下载...") return download_bark_server() ``` ```python # --- Start Bark Server as a background process --- bark_available = ensure_bark_server() if bark_available: print(f"📱 [0/4] 启动 Bark 推送服务 (port {PORT_BARK})...") bark_data_dir = os.path.join(PROJECT_ROOT, "data", "bark") ...[truncated 2381 chars]
Remediation
View remediation
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (470)

Tainted flow: 'LOCAL_OPENAI_COMPLETIONS_URL' from os.getenv (line 31, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

This endpoint accepts arbitrary POST bodies and forwards the caller's Authorization header directly to the internal agent service, while also returning permissive CORS headers on OPTIONS. In a system exposing powerful local agent capabilities, this creates a confused-deputy proxy that can let web clients drive sensitive backend actions through the frontend without strong validation or origin restrictions.

Content

Scanner excerpt · src/front.py (reported line 146)May include surrounding context.

python
# 直接透传请求体和 Authorization header 到后端
    auth_header = request.headers.get("Authorization", "")
    try:
        r = requests.post(
            LOCAL_OPENAI_COMPLETIONS_URL,
            json=request.get_json(silent=True),
            headers={

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · src/front.py (reported line 185)May include surrounding context.

python
def proxy_openai_models():
    """透传 /v1/models"""
    try:
        r = requests.get(f"http://127.0.0.1:{PORT_AGENT}/v1/models", timeout=10)
        return Response(r.content, status=r.status_code, content_type=r.headers.get("content-type", "application/json"))
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'LOCAL_LOGIN_URL' from os.getenv (line 23, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
98% confidence
Finding

The login handler forwards raw credentials to a backend and then stores the password in the Flask session for later reuse. Storing reusable credentials server-side in session state increases exposure through session compromise, memory disclosure, logging mistakes, or weak cookie/session protections.

Content

Scanner excerpt · src/front.py (reported line 198)May include surrounding context.

python
password = request.json.get("password", "")

    try:
        r = requests.post(LOCAL_LOGIN_URL, json={"user_id": user_id, "password": password}, timeout=10)
        if r.status_code == 200:
            # 登录成功,在 Flask session 中记录
            session["user_id"] = user_id

Tainted flow: 'LOCAL_AGENT_CANCEL_URL' from os.getenv (line 22, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

This endpoint reuses credentials stored in session and forwards them to the backend to trigger cancellation actions. The core issue is the insecure credential handling pattern; compromise of the frontend session enables backend action execution as the user.

Content

Scanner excerpt · src/front.py (reported line 230)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    session_id = request.json.get("session_id", "default") if request.is_json else "default"
    try:
        r = requests.post(LOCAL_AGENT_CANCEL_URL, json={"user_id": user_id, "password": password, "session_id": session_id}, timeout=5)
        return jsonify(r.json())
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'LOCAL_TTS_URL' from os.getenv (line 28, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The TTS proxy forwards authenticated user data and returns raw media from the backend without additional authorization controls beyond the session. Because credentials are stored and replayed, a stolen session can be abused to invoke backend functionality and potentially process sensitive text.

Content

Scanner excerpt · src/front.py (reported line 252)May include surrounding context.

python
payload = {"user_id": user_id, "password": password, "text": text}
        if voice:
            payload["voice"] = voice
        r = requests.post(LOCAL_TTS_URL, json=payload, timeout=60)
        if r.status_code != 200:
            return jsonify({"error": f"TTS 服务错误: {r.status_code}"}), r.status_code

Tainted flow: 'LOCAL_TOOLS_URL' from os.getenv (line 24, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
91% confidence
Finding

The frontend exposes an internal tools listing endpoint and authenticates to the backend with a static internal token from environment. If this frontend is reachable by untrusted users, it can disclose privileged internal capability metadata that should not be broadly exposed.

Content

Scanner excerpt · src/front.py (reported line 268)May include surrounding context.

python
def proxy_tools():
    """代理获取工具列表请求到后端 Agent"""
    try:
        r = requests.get(LOCAL_TOOLS_URL, headers={"X-Internal-Token": INTERNAL_TOKEN}, timeout=10)
        return jsonify(r.json())
    except Exception as e:
        return jsonify({"error": str(e), "tools": []}), 500

Tainted flow: 'LOCAL_SETTINGS_URL' from os.getenv (line 279, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
97% confidence
Finding

User credentials are sent as URL query parameters to the backend on a GET request. Query-string credentials are especially risky because they may be logged by servers, proxies, monitoring systems, and browser tooling more easily than body data or headers.

Content

Scanner excerpt · src/front.py (reported line 290)May include surrounding context.

python
if not user_id or not password:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.get(LOCAL_SETTINGS_URL, params={"user_id": user_id, "password": password}, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'LOCAL_SETTINGS_URL' from os.getenv (line 279, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

This settings update route injects stored user credentials into outbound requests to the backend. The repeated credential replay pattern magnifies risk from any session compromise and unnecessarily broadens credential exposure surface.

Content

Scanner excerpt · src/front.py (reported line 307)May include surrounding context.

python
data = request.get_json(force=True)
        data["user_id"] = user_id
        data["password"] = password
        r = requests.post(LOCAL_SETTINGS_URL, json=data, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'LOCAL_SESSIONS_URL' from os.getenv (line 25, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The session-list proxy forwards stored credentials to retrieve user data from the backend. While common in a proxy design, the insecure aspect is retaining and replaying passwords for routine actions rather than using scoped tokens.

Content

Scanner excerpt · src/front.py (reported line 321)May include surrounding context.

python
if not user_id or not password:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.post(LOCAL_SESSIONS_URL, json={"user_id": user_id, "password": password}, timeout=15)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

This route forwards stored credentials to an internal status endpoint for authenticated actions. The danger comes from session-based credential replay enabling unauthorized backend access if the frontend session is hijacked or abused via CSRF.

Content

Scanner excerpt · src/front.py (reported line 335)May include surrounding context.

python
if not user_id or not password:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.post(
            f"http://127.0.0.1:{PORT_AGENT}/sessions_status",
            json={"user_id": user_id, "password": password},
            timeout=5,

Tainted flow: 'OASIS_BASE_URL' from os.getenv (line 36, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · src/front.py (reported line 350)May include surrounding context.

python
"""Proxy to fetch OpenClaw session list from OASIS server."""
    filter_kw = request.args.get("filter", "")
    try:
        r = requests.get(
            f"{OASIS_BASE_URL}/sessions/openclaw",
            params={"filter": filter_kw},
            timeout=10,

Tainted flow: 'LOCAL_SESSION_HISTORY_URL' from os.getenv (line 26, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The session-history endpoint replays stored credentials to fetch message history from the backend. If the frontend session is compromised, an attacker can access potentially sensitive conversation history through this proxy.

Content

Scanner excerpt · src/front.py (reported line 369)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    sid = request.json.get("session_id", "")
    try:
        r = requests.post(LOCAL_SESSION_HISTORY_URL, json={
            "user_id": user_id, "password": password, "session_id": sid
        }, timeout=15)
        return jsonify(r.json()), r.status_code

Tainted flow: 'LOCAL_SESSION_STATUS_URL' from os.getenv (line 29, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

This route forwards stored credentials to check session status, continuing the same insecure authentication pattern. Although lower impact than destructive actions, it still leaks access via session compromise and supports user activity enumeration.

Content

Scanner excerpt · src/front.py (reported line 386)May include surrounding context.

python
return jsonify({"has_new_messages": False}), 200
    sid = request.json.get("session_id", "") if request.is_json else ""
    try:
        r = requests.post(LOCAL_SESSION_STATUS_URL, json={
            "user_id": user_id, "password": password, "session_id": sid
        }, timeout=5)
        return jsonify(r.json()), r.status_code

Tainted flow: 'LOCAL_DELETE_SESSION_URL' from os.getenv (line 27, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The delete-session proxy enables destructive backend actions using credentials stored in the frontend session. A hijacked browser session or cross-site request against this endpoint could irreversibly delete user data or workflow history.

Content

Scanner excerpt · src/front.py (reported line 403)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    sid = request.json.get("session_id", "") if request.is_json else ""
    try:
        r = requests.post(LOCAL_DELETE_SESSION_URL, json={
            "user_id": user_id, "password": password, "session_id": sid
        }, timeout=15)
        return jsonify(r.json()), r.status_code

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

Group chat APIs use an Authorization header built as Bearer user_id:password, embedding plaintext credentials into a bearer token format and forwarding them to the backend. This is a weak authentication design that increases credential exposure and makes any leaked header immediately reusable.

Content

Scanner excerpt · src/front.py (reported line 429)May include surrounding context.

python
if not uid:
        return jsonify([]), 200
    try:
        r = requests.get(f"http://127.0.0.1:{PORT_AGENT}/groups", headers=headers, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

This group-creation route forwards arbitrary request JSON together with a header containing plaintext credentials encoded as a bearer token. The issue is not the proxy itself but the insecure credential construction and replay across many state-changing endpoints.

Content

Scanner excerpt · src/front.py (reported line 443)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    try:
        headers["Content-Type"] = "application/json"
        r = requests.post(f"http://127.0.0.1:{PORT_AGENT}/groups", json=request.get_json(silent=True), headers=headers, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

Fetching group details relies on the same insecure header format carrying raw credentials to the backend. This unnecessarily expands exposure of user secrets across multiple requests and endpoints.

Content

Scanner excerpt · src/front.py (reported line 456)May include surrounding context.

python
if not uid:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.get(f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}", headers=headers, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.put (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

Updating group state is a privileged action performed through a proxy that sends plaintext-derived bearer credentials to the backend. If a session or header is stolen, an attacker can modify group configuration and potentially disrupt multi-agent workflows or messaging.

Content

Scanner excerpt · src/front.py (reported line 470)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    try:
        headers["Content-Type"] = "application/json"
        r = requests.put(f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}", json=request.get_json(silent=True), headers=headers, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

This messages endpoint exposes group message retrieval through a credential-replay proxy. Compromise of the frontend session enables reading potentially sensitive group communications and coordination data.

Content

Scanner excerpt · src/front.py (reported line 497)May include surrounding context.

python
return jsonify({"messages": []}), 200
    try:
        after_id = request.args.get("after_id", "0")
        r = requests.get(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/messages",
            params={"after_id": after_id},
            headers=headers, timeout=10,

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

Posting group messages through the proxy permits authenticated state-changing actions using replayed credentials. In this orchestration product, forged messages could manipulate operators or downstream agents and alter collaborative workflows.

Content

Scanner excerpt · src/front.py (reported line 515)May include surrounding context.

python
return jsonify({"error": "未登录"}), 401
    try:
        headers["Content-Type"] = "application/json"
        r = requests.post(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/messages",
            json=request.get_json(silent=True),
            headers=headers, timeout=10,

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

Muting a group is a privileged state change performed with replayed credentials from the frontend. Session compromise can be used to disrupt notifications and coordination among users or agents.

Content

Scanner excerpt · src/front.py (reported line 532)May include surrounding context.

python
if not uid:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.post(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/mute",
            headers=headers, timeout=10,
        )

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

Unmuting uses the same insecure pattern as muting, allowing backend state changes through a credential-replay proxy. This supports unauthorized manipulation of user communications if the frontend session is abused.

Content

Scanner excerpt · src/front.py (reported line 548)May include surrounding context.

python
if not uid:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.post(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/unmute",
            headers=headers, timeout=10,
        )

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
92% confidence
Finding

Mute-status retrieval is read-only but still depends on forwarding raw-credential-derived authorization to the backend. The main risk is unnecessary credential exposure and information disclosure through session compromise.

Content

Scanner excerpt · src/front.py (reported line 564)May include surrounding context.

python
if not uid:
        return jsonify({"muted": False}), 200
    try:
        r = requests.get(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/mute_status",
            headers=headers, timeout=10,
        )

Tainted flow: 'PORT_AGENT' from os.getenv (line 18, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
92% confidence
Finding

This route reveals which sessions can join a group via a proxy authorized by replayed credentials. In a multi-agent system, such metadata can expose topology, available agents, or active workflow structure to anyone who obtains the session.

Content

Scanner excerpt · src/front.py (reported line 580)May include surrounding context.

python
if not uid:
        return jsonify({"sessions": []}), 200
    try:
        r = requests.get(
            f"http://127.0.0.1:{PORT_AGENT}/groups/{group_id}/sessions",
            headers=headers, timeout=15,
        )

Tainted flow: 'OASIS_BASE_URL' from os.getenv (line 36, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · src/front.py (reported line 599)May include surrounding context.

python
return jsonify([]), 200
    try:
        print(f"[OASIS Proxy] Fetching topics from {OASIS_BASE_URL}/topics for user={user_id}")
        r = requests.get(f"{OASIS_BASE_URL}/topics", params={"user_id": user_id}, timeout=10)
        print(f"[OASIS Proxy] Response status: {r.status_code}, count: {len(r.json()) if r.text else 0}")
        return jsonify(r.json()), r.status_code
    except Exception as e:

Static analysis

No suspicious patterns detected.