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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
- 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`.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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 · scripts/langfuse_openclaw_optimizer.py (reported line 396)May include surrounding context.

python
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:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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/langfuse_openclaw_optimizer.py (reported line 105)May include surrounding context.

python
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

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/langfuse_openclaw_optimizer.py (reported line 115)May include surrounding context.

python
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

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/langfuse_openclaw_optimizer.py (reported line 128)May include surrounding context.

python
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

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/langfuse_openclaw_optimizer.py (reported line 610)May include surrounding context.

python
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

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/langfuse_openclaw_optimizer.py (reported line 630)May include surrounding context.

python
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

Static analysis

No suspicious patterns detected.