Back to skill

Security audit

Clawfuse

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does what it claims, but it handles LangFuse credentials and telemetry in ways that need careful review before installation.

Install only if you are comfortable giving the skill LangFuse API access and storing telemetry-derived artifacts locally. Use a trusted HTTPS LangFuse host, avoid --persist-secrets, restrict the output directory permissions, periodically delete raw snapshots, and review any generated routing policy before enabling daemon mode or --promote-live-policy.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/langfuse_openclaw_optimizer.py:143
Finding
Langfuse credentials can be transmitted to an unrestricted user-configurable host## Vulnerability Details **File Location**: `scripts/langfuse_openclaw_optimizer.py`, lines 143-146, 193-210, 418-445, and 520-522 **Vulnerability Type**: Unrestricted credential transmission destination **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python def basic_auth_header(public_key: str, secret_key: str) -> str: raw = f"{public_key}:{secret_key}".encode("utf-8") token = base64.b64encode(raw).decode("ascii") return f"Basic {token}" ``` ```python query = urlencode(params, doseq=True) url = f"{host}{endpoint}?{query}" req = Request( url=url, method="GET", headers={ "Authorization": auth_header, "Accept": "application/json", }, ) with urlopen(req, timeout=timeout_sec) as resp: payload = json.loads(resp.read().decode("utf-8")) ``` ```python auth = basic_auth_header(public_key, secret_key) obs_params = {} if args.environment: obs_params["environment"] = args.environment observations = fetch_langfuse_items( host=args.langfuse_host, endpoint="/api/public/v2/observations", auth_header=auth, from_ts=from_ts, to_ts=to_ts, limit=args.limit, max_pages=args.max_pages, extra_params=obs_params, timeout_sec=args.http_timeout_sec, ) ``` ```python p.add_argument("--langfuse-host", default=None, help="LangFuse host URL.") p.add_argument("--langfuse-public-key", default=None, help="LangFuse public key (or LANGFUSE_PUBLIC_KEY env).") p.add_argument("--langfuse-secret-key", default=None, help="LangFuse secret key (or LANGFUSE_SECRET_KEY env).") ``` ### Technical Analysis The Base64 operation is the normal encoding required by HTTP Basic au ...[truncated 2162 chars]
Remediation
## Remediation Suggestions 1. Parse the configured host with `urllib.parse.urlsplit` and reject every scheme other than `https`. 2. Use an explicit allowlist of trusted Langfuse origins by default, such as `https://us.cloud.langfuse.com`. 3. For self-hosted deployments, require an explicit opt-in flag and display or log the normalized credential destination before sending a request. 4. Reject URLs containing user information, fragments, unexpected paths, malformed ports, or ambiguous hostnames. 5. Prevent silent cross-origin redirects for authenticated requests, or strip the `Authorization` header whenever the origin changes. 6. Protect the persisted configuration with restrictive filesystem permissions so untrusted local users cannot replace the host. 7. Use dedicated, read-only, minimally scoped Langfuse credentials for telemetry retrieval. 8. Document that changing `langfuse_host` changes the destination receiving the Langfuse secret key.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/langfuse_openclaw_optimizer.py:80
Finding
Sensitive Langfuse telemetry and optional API secrets are persisted without explicit filesystem protection## Vulnerability Details **File Location**: `scripts/langfuse_openclaw_optimizer.py`, lines 80-85, 414-435, 519, and 599-617 **Vulnerability Type**: Plaintext sensitive-data storage and unbounded raw telemetry retention **Risk Level**: Medium **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python def write_json(path: Path, payload: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, indent=2, ensure_ascii=True), encoding="utf-8") ``` ```python out_dir = Path(args.out_dir).expanduser() raw_dir = out_dir / "raw" / stamp cycle_dir = out_dir / "cycles" / stamp raw_dir.mkdir(parents=True, exist_ok=True) cycle_dir.mkdir(parents=True, exist_ok=True) auth = basic_auth_header(public_key, secret_key) obs_params = {} if args.environment: obs_params["environment"] = args.environment observations = fetch_langfuse_items( host=args.langfuse_host, endpoint="/api/public/v2/observations", auth_header=auth, from_ts=from_ts, to_ts=to_ts, limit=args.limit, max_pages=args.max_pages, extra_params=obs_params, timeout_sec=args.http_timeout_sec, ) raw_obs_file = raw_dir / "langfuse_observations.json" raw_obs_file.write_text(json.dumps(observations, ensure_ascii=True, indent=2), encoding="utf-8") ``` ```python def persist_current_settings( args: argparse.Namespace, cfg_path: Path, base_config: Optional[dict] = None, ) -> None: payload = dict(base_config or {}) payload.update({k: getattr(args, k) for k in CONFIG_KEYS if hasattr(args, k)}) if args.persist_secrets: if getattr(args, "langfuse_public_key", None): payload["langfuse_public_key"] = args.langfuse_public_key if getattr(args, "langfuse_secret_key", None): ...[truncated 2841 chars]
Remediation
## Remediation Suggestions 1. Create the optimizer root, raw-data directories, and configuration directories with mode `0700`. 2. Create sensitive files atomically with mode `0600`, using a safely opened temporary file followed by `os.replace`. 3. Avoid storing raw `input`, `output`, and unrestricted metadata unless the operator explicitly enables raw retention. 4. Redact known secret fields and provide a configurable field allowlist before writing telemetry. 5. Make minimized normalized records the default persisted artifact and keep only hashes, counts, and aggregate measurements required for optimization. 6. Add configurable age-, count-, and size-based retention limits for timestamped raw snapshots. 7. Do not persist API secrets in JSON. Use environment variables, an operating-system credential store, or a dedicated secrets manager. 8. If secret persistence must remain supported, encrypt the stored values using a key held outside the configuration directory and clearly warn the operator. 9. Document that raw Langfuse observations may contain prompt and output content and may be copied by backup or synchronization systems.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Tp4

High
Category
MCP Tool Poisoning
Confidence
87% confidence
Finding
The skill description claims an automated continuous optimizer with safe promotion cycles and persistent memory, but the static finding indicates the implementation may not actually provide those safeguards or behaviors. Security-relevant description/behavior mismatches are dangerous because operators may grant trust, secrets, or autonomy based on claimed safety properties that are absent, leading to unsafe deployment assumptions and unreviewed policy changes.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises substantial capabilities—environment variable access, network egress, file reads/writes, and shell execution—but does not declare an explicit tool scope such as permissions or allowed-tools. This weakens least-privilege controls and makes it harder for a caller or runtime to understand and constrain what the skill can do, increasing the risk of unintended secret access, filesystem modification, or outbound data transfer.

Session Persistence

Medium
Category
Rogue Agent
Content
- Network egress: calls LangFuse Public API.
- Local writes: writes raw snapshots, staged artifacts, and optional memory state under `--out-dir`.
- Live policy overwrite is opt-in via `--promote-live-policy`.
- Without `--promote-live-policy`, cycles are non-destructive (stage/evaluate only).
- Save persisted defaults with `--save-config`; edit/toggle with `configure`.
Confidence
82% confidence
Finding
The skill persists optimizer state, raw snapshots, configuration, and optional live policy changes to local storage, creating session persistence across runs. In this context, persistence is security-relevant because stored telemetry, policy artifacts, or saved defaults can expose sensitive operational data, survive beyond the current session, and influence later executions in ways that may be hard to audit or roll back.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script normalizes LangFuse/OpenRouter telemetry and writes it to JSONL/CSV outputs, including fields derived from prompt input such as prompt_char_count and input_sha256, and it can persist policy/statistics artifacts without any explicit privacy warning, consent gate, redaction, or minimization control. In this skill’s context, the input telemetry may contain user prompts, conversation text, and sensitive operational metadata, so silently persisting derived prompt data to disk increases the risk of unintended retention, later disclosure, or misuse on shared systems.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd.extend(["--max-latency-p95-ms", str(args.max_latency_p95_ms)])
    if scores_file and scores_file.exists():
        cmd.extend(["--scores", str(scores_file)])
    subprocess.run(cmd, check=True)


def run_once(args: argparse.Namespace) -> int:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The skill allows LangFuse API credentials to be written into a local JSON config when --persist-secrets is used, creating a durable plaintext secret store under the user's home directory. If the file is exposed through weak filesystem permissions, backups, logs, or later local compromise, an attacker can recover credentials and access LangFuse telemetry or related data.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Although secret persistence requires flags, the code provides no strong user-facing warning at the time credentials may be stored to disk, increasing the chance of unsafe operational use. In a telemetry optimizer context, these secrets grant API access and could expose observations, scores, and potentially sensitive prompt/output metadata if the config file is later read by other processes or users.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def apply_effective_config(args: argparse.Namespace, config: dict) -> argparse.Namespace:
    for key, default in CONFIG_DEFAULTS.items():
        cli_value = getattr(args, key, None)
        if cli_value is not None:
            setattr(args, key, cli_value)
            continue
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def apply_effective_config(args: argparse.Namespace, config: dict) -> argparse.Namespace:
    for key, default in CONFIG_DEFAULTS.items():
        cli_value = getattr(args, key, None)
        if cli_value is not None:
            setattr(args, key, cli_value)
            continue
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
overrides = {}
    for key in CONFIG_KEYS.union(SENSITIVE_KEYS):
        if hasattr(args, key):
            value = getattr(args, key)
            if value is not None:
                overrides[key] = value
    return overrides
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
base_config: Optional[dict] = None,
) -> None:
    payload = dict(base_config or {})
    payload.update({k: getattr(args, k) for k in CONFIG_KEYS if hasattr(args, k)})
    if args.persist_secrets:
        if getattr(args, "langfuse_public_key", None):
            payload["langfuse_public_key"] = args.langfuse_public_key
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
args = apply_effective_config(args, base_cfg)
        persist_current_settings(args, cfg_path, base_config=base_cfg)
        if args.show:
            shown = {k: getattr(args, k) for k in sorted(CONFIG_KEYS)}
            print(json.dumps(shown, indent=2, ensure_ascii=True))
        return 0
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.