Back to skill

Security audit

TeamClaw

Security checks across malware telemetry and agentic risk

Overview

TeamClaw’s multi-agent features are coherent, but it exposes broad agent, file, command, and public-web controls with weak credential handling that users should review before installing.

Install only if you are comfortable running a local agent platform with command execution, file access, network access, persistent memory, bot integrations, and optional public exposure. Avoid enabling the Cloudflare tunnel unless you add strong access controls, and do not reuse important passwords because this version passes and stores raw user passwords internally.

SkillSpector

By NVIDIA
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 (87)

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

Critical
Category
Data Flow
Content
# 直接透传请求体和 Authorization header 到后端
    auth_header = request.headers.get("Authorization", "")
    try:
        r = requests.post(
            LOCAL_OPENAI_COMPLETIONS_URL,
            json=request.get_json(silent=True),
            headers={
Confidence
97% confidence
Finding
The endpoint accepts any Authorization header from the client and forwards it directly to the internal agent service, while also enabling Access-Control-Allow-Origin: * for the same route. If the internal agent trusts bearer credentials presented through this proxy, a malicious website or unauthorized caller may be able to drive privileged agent actions or abuse the proxy as an authentication relay.

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

Critical
Category
Data Flow
Content
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
Confidence
95% confidence
Finding
The proxy stores the user's password directly in the Flask session after login. If session data is exposed through weak cookie settings, debugging, or downstream compromise, plaintext credentials for the backend agent are recoverable and reusable.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
This endpoint forwards the user's stored password to the backend on each cancel request, perpetuating plaintext credential handling across the application. The repeated use of session-stored credentials broadens the exposure surface and increases impact if the session is compromised.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
The TTS proxy includes plaintext user credentials in requests to the backend service. This continues an unsafe credential propagation pattern and exposes passwords to logs, crash dumps, memory inspection, and any compromise of intermediary components.

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

Critical
Category
Data Flow
Content
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
Confidence
88% confidence
Finding
The route exposes an internal tools endpoint and authenticates to it using a single INTERNAL_TOKEN from environment, but the proxy itself does not require user authentication. If the backend token authorizes privileged tool enumeration, any unauthenticated caller can leverage this frontend as a capability oracle against internal functionality.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
The code sends user_id and password as URL query parameters on a GET request. Query parameters are commonly logged by servers, proxies, browser tooling, and monitoring systems, making credential leakage far more likely than body- or header-based transport.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
This endpoint forwards the user's plaintext password in the JSON payload to the backend settings service. While localhost transport reduces network exposure, the credentials remain exposed to application logs, exceptions, and process memory, and the pattern repeats across many routes.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
The sessions proxy transmits plaintext credentials to the internal service on every request. This is part of a systemic credential handling weakness that increases the blast radius of any session compromise or local service logging issue.

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

Critical
Category
Data Flow
Content
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,
Confidence
96% confidence
Finding
This route also forwards plaintext credentials for session status checks. Reusing the same password across numerous internal calls unnecessarily multiplies exposure points and makes credential theft more likely during routine operations.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
The session history proxy transmits stored user credentials in each backend call. This continues the same insecure credential management pattern and increases the number of code paths that can leak or misuse passwords.

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

Critical
Category
Data Flow
Content
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
Confidence
96% confidence
Finding
The route posts plaintext credentials to check session status, again expanding the credential exposure surface. Even if localhost-only, plaintext secrets in repeated request bodies are a poor design choice with real leakage risk through logs and diagnostics.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
Deleting sessions is a destructive action authenticated with plaintext credentials stored in session and reused in backend calls. If an attacker gains the victim's session, they can trigger destructive actions without needing the original password and the stored password may also be extracted or reused elsewhere.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
Group APIs derive an Authorization header as Bearer user_id:password, effectively embedding plaintext credentials into an ad hoc bearer token format. This is unsafe because credentials may be logged as headers by reverse proxies or application telemetry and are reusable if disclosed.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
Creating groups uses the same Bearer user_id:password pattern, exposing raw credentials inside an Authorization header for a state-changing action. This weak custom authentication scheme increases credential disclosure and replay risk.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
Fetching group details transmits raw credentials in the Authorization header using a nonstandard bearer format. Any system that logs or inspects headers can inadvertently capture reusable credentials.

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

Critical
Category
Data Flow
Content
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
Confidence
97% confidence
Finding
Updating groups is a state-changing operation authenticated by a bearer header that contains plaintext credentials. This both leaks secrets and makes replay attacks easier if the header is observed.

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

Critical
Category
Data Flow
Content
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,
Confidence
97% confidence
Finding
Reading group messages still relies on the custom Authorization header containing user_id and password. Sensitive chat access combined with credential exposure increases the impact of header leakage.

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

Critical
Category
Data Flow
Content
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,
Confidence
97% confidence
Finding
Posting group messages is a state-changing action authenticated with a bearer header that directly encodes user credentials. If exposed, an attacker can impersonate the user for messaging and potentially other group operations.

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

Critical
Category
Data Flow
Content
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,
        )
Confidence
97% confidence
Finding
Muting groups uses the same insecure custom bearer format. Although localhost transport narrows exposure, credential leakage in logs or exception traces can still lead to account misuse.

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

Critical
Category
Data Flow
Content
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,
        )
Confidence
97% confidence
Finding
Unmuting groups also uses credentials inside the Authorization header. This repeats the same unsafe credential propagation design and exposes secrets during a privileged state change.

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

Critical
Category
Data Flow
Content
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,
        )
Confidence
97% confidence
Finding
Querying mute status sends raw credentials in headers to the backend. Even for a read action, exposing reusable secrets in transit through application layers is unnecessary and dangerous.

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

Critical
Category
Data Flow
Content
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,
        )
Confidence
97% confidence
Finding
Listing sessions eligible for group joining again uses Bearer user_id:password authentication. This pattern is insecure across the entire group chat feature set and should be treated as a systemic vulnerability rather than an isolated issue.

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

Critical
Category
Data Flow
Content
if not user_id:
        return jsonify({"error": "未登录"}), 401
    try:
        r = requests.post(f"{OASIS_BASE_URL}/topics/{topic_id}/purge", params={"user_id": user_id}, timeout=10)
        return jsonify(r.json()), r.status_code
    except Exception as e:
        return jsonify({"error": str(e)}), 500
Confidence
93% confidence
Finding
This route performs a destructive purge action based only on the Flask session's user_id and lacks any visible CSRF protection. In a browser context, a cross-site request from another origin could trigger authenticated deletion of OASIS topic data if the victim is logged in.

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

Critical
Category
Data Flow
Content
"session_id": data.get("target_session_id") or "visual_orchestrator",
            "temperature": 0.3,
        }
        resp = requests.post(LOCAL_OPENAI_COMPLETIONS_URL, json=payload, headers=headers, timeout=60)
        if resp.status_code != 200:
            return jsonify({"prompt": prompt, "error": f"Agent returned HTTP {resp.status_code}: {resp.text[:500]}", "agent_yaml": None})
Confidence
97% confidence
Finding
The visual YAML generation route again authenticates to the backend with Bearer user_id:password and sends LLM-generated content that may later be auto-saved. This combines insecure credential handling with a sensitive orchestration feature, increasing the risk of credential leakage and unauthorized workflow generation if the session is abused.

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

Critical
Category
Data Flow
Content
if not user_id or not password:
        return jsonify([])
    try:
        r = requests.post(LOCAL_SESSIONS_URL, json={"user_id": user_id, "password": password}, timeout=10)
        if r.status_code != 200:
            return jsonify([])
        sessions_data = r.json()
Confidence
96% confidence
Finding
This endpoint forwards plaintext credentials to list sessions for the visual canvas. It is another instance of systemic secret propagation across internal API calls, increasing overall credential exposure.

VirusTotal

63/63 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

No suspicious patterns detected.