Back to skill

Security audit

LLM Loop Breaker

Security checks for vulnerabilities and agentic risk

Overview

This skill is a defensive gateway guard, but it persistently patches the gateway, starts a detached watchdog, can kill process trees, and has unsafe shell/path handling that users should review before installing.

Install only after reviewing the deploy script and accepting that it will modify openclaw.mjs, patch global fetch, start a background watchdog, collect local host diagnostics, and automatically kill selected gateway child processes. Prefer running it in a test environment first, under a dedicated unprivileged account, with pinned dependencies, explicit service management, restricted log paths, and a clear rollback plan.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
Findings (5)

T07 · Tool Hijacking and Spoofing

Error
Location
src/stream-entropy-breaker.cjs:132
Finding
Process-Wide Hijacking of the Global Fetch API<![CDATA[ ## Vulnerability Details **File Location**: `src/stream-entropy-breaker.cjs:132-137`; activation occurs at `deploy.sh:95-99` **Vulnerability Type**: Process-wide API replacement **Risk Level**: High ### Vulnerable Code `src/stream-entropy-breaker.cjs:132-137`: ```js function install() { if (global.__streamEntropyBreakerInstalled) return; if (!global.__originalFetch) global.__originalFetch = fetch; global.fetch = patchedFetch; global.__streamEntropyBreakerInstalled = true; } ``` `deploy.sh:95-99`: ```js const breaker = require("./dist/llm_stream_guard/stream-entropy-breaker.cjs"); if (breaker && breaker.install) { breaker.install(); } ``` ### Technical Analysis The installation function replaces Node.js's process-wide `global.fetch` implementation with `patchedFetch`. Consequently, every component in the gateway that uses the global Fetch API is routed through Skill-controlled logic. The interceptor does not restrict itself to configured LLM providers or approved endpoint URLs. Instead, it examines any response whose content type includes `text/event-stream`, `application/x-ndjson`, or `application/stream+json`. Its entropy heuristics can then abort the associated request. Because these content types are also used by legitimate non-LLM streaming APIs, the replacement can affect unrelated gateway integrations. Entropy heuristics are not a reliable security boundary and may produce false positives for intentionally repetitive data. ### Attack Path 1. An administrator runs `deploy.sh`. 2. The script appends bootstrap code to `openclaw.mjs`. 3. On the next gateway start, the bootstrap imports the stream breaker and invokes `install()`. 4. `install()` saves the original function and replaces `global.fetch`. 5. Any gateway module making a Fetch API request now invokes `patchedFetch`. 6. If the response uses one of the broadly matched streaming content types, its body is intercepted. 7. A sufficiently repetitive legitimate stream can trigg ...[truncated 576 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not replace `global.fetch`. - Integrate the breaker explicitly into the approved LLM client or request path. - Restrict interception to an administrator-configured allowlist of provider origins, endpoint paths, methods, and response types. - Make the feature opt-in and document the exact process-wide effects before installation. - Add false-positive handling, observability, and a configurable fail-open mode. - Preserve and remove request-signal event listeners when requests complete. - Provide an automated rollback mechanism that restores the original application entry point and networking behavior. - Add tests covering legitimate repetitive SSE and NDJSON responses from non-LLM services. ]]>

T06 · System Persistence

Error
Location
deploy.sh:66
Finding
Persistent Gateway Modification and Detached Watchdog Execution<![CDATA[ ## Vulnerability Details **File Location**: `deploy.sh:66-101` **Vulnerability Type**: Persistent application startup hook **Risk Level**: High ### Vulnerable Code ```bash if grep -q "${MARKER}" "${TARGET_FILE}"; then echo "[LLM Stream Guard] Already injected into ${TARGET_FILE}. Skipping." else BACKUP_FILE="${TARGET_FILE}.bak.$(date +%Y%m%d_%H%M%S)" cp "${TARGET_FILE}" "${BACKUP_FILE}" echo "[LLM Stream Guard] Backup created: ${BACKUP_FILE}" cat >> "${TARGET_FILE}" << 'INJECT' // [LLM_STREAM_GUARD_START] -- Injected by deploy.sh. Do not edit manually. // Layer 2: Pull up Host Resource Watchdog daemon try { const { execSync } = module.createRequire(import.meta.url)("child_process"); try { execSync('pgrep -f "[h]ost-resource-watchdog.py"'); } catch (err) { const distDir = new URL("dist/llm_stream_guard/", import.meta.url).pathname; const logDir = (process.env.HOME || "/root") + "/.openclaw/workspace/memory/core"; execSync(`mkdir -p "${logDir}"`); execSync(`nohup python3 "${distDir}host-resource-watchdog.py" > "${logDir}/host_watchdog.log" 2>&1 &`); console.log("[Host Resource Watchdog] Started."); } } catch (e) { console.error("[Host Resource Watchdog] Failed to start:", e.message); } // Layer 1: Activate Stream Entropy Breaker (patches global.fetch) try { const { createRequire } = await import("node:module"); const require = createRequire(import.meta.url); const breaker = require("./dist/llm_stream_guard/stream-entropy-breaker.cjs"); if (breaker && breaker.install) { breaker.install(); } } catch (e) { console.error("[Stream Entropy Breaker] Failed to activate:", e); } // [LLM_STREAM_GUARD_END] INJECT ``` ### Technical Analysis The deployment process permanently appends executable code to the gateway's primary entry point, `openclaw.mjs`. The injected code runs on every subsequent gateway start. The startup hook searches globally for a process whose command line matches `host-resource-w ...[truncated 1611 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not append executable content directly to `openclaw.mjs`. - Package the integration through a documented Openclaw extension or middleware interface. - If a daemon is genuinely required, install it only after explicit administrator approval through a service manager such as systemd. - Run the daemon under a dedicated unprivileged account with a restrictive service sandbox. - Define restart policy, process ownership, resource limits, and logging in the service configuration. - Use a PID file or service-manager identity rather than a global `pgrep -f` search. - Verify deployed file ownership and integrity before execution. - Provide a deterministic uninstall command that removes the integration, stops the daemon, and restores the original entry point. - Require explicit confirmation before modifying application startup files. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
deploy.sh:82
Finding
Shell Command Injection Through the HOME Environment Variable<![CDATA[ ## Vulnerability Details **File Location**: `deploy.sh:82-84` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code The following JavaScript is inserted into `openclaw.mjs` by the deployment script: ```js const logDir = (process.env.HOME || "/root") + "/.openclaw/workspace/memory/core"; execSync(`mkdir -p "${logDir}"`); execSync(`nohup python3 "${distDir}host-resource-watchdog.py" > "${logDir}/host_watchdog.log" 2>&1 &`); ``` ### Technical Analysis `process.env.HOME` is environment-controlled input. It is concatenated into command strings and passed to `execSync`, which invokes a shell. Placing the value inside double quotes does not make it safe. POSIX shells still evaluate command substitution such as `$(command)` and backticks inside double quotes. A malicious value can also contain a double quote to terminate the quoted path and inject additional shell syntax. For example, a launch environment containing a `HOME` value with command substitution would cause that substitution to execute when the gateway evaluates the injected startup block. The exact ability to set the gateway environment depends on the deployment environment, but no validation or shell-safe argument handling is present. ### Attack Path 1. An attacker gains the ability to influence the gateway's launch environment, deployment configuration, container environment, or service environment file. 2. The attacker assigns a crafted value to `HOME`, containing shell substitution or quote-breaking syntax. 3. The gateway starts and executes the block injected by `deploy.sh`. 4. The crafted value is interpolated into the `mkdir` and `nohup` command strings. 5. `execSync` invokes a shell. 6. The shell evaluates the injected syntax with the gateway process's operating-system privileges. ### Impact Assessment Successful exploitation permits arbitrary command execution as the gateway account. The attacker can read or modify files accessible to that account ...[truncated 341 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Eliminate shell command construction for filesystem and process operations. - Replace `execSync("mkdir ...")` with `fs.mkdirSync(logDir, { recursive: true })`. - Launch Python with `spawn()` or `execFile()` and an argument array, without `shell: true`. - Open the log file through Node.js and pass its descriptor through the `stdio` option rather than using shell redirection. - Resolve and normalize `HOME`, then verify that the resulting path is inside an approved base directory. - Reject environment paths containing NUL characters or paths outside the configured workspace. - Configure the log directory through a trusted administrator-owned configuration file rather than inheriting an unrestricted environment variable. - Run the gateway and watchdog under a dedicated unprivileged account. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
host-resource-watchdog.py:153
Finding
Overbroad Process Identification Enables Destructive Termination of Legitimate Workloads<![CDATA[ ## Vulnerability Details **File Location**: `host-resource-watchdog.py:153-162`; destructive operation at `host-resource-watchdog.py:275-290` **Vulnerability Type**: Insufficient process identity validation and overprivileged process termination **Risk Level**: High ### Vulnerable Code Gateway discovery at `host-resource-watchdog.py:153-162`: ```python for p in psutil.process_iter(['pid', 'name', 'cmdline']): try: cmdline = p.info.get('cmdline', []) name = p.info.get('name', '') if cmdline is None: cmdline = [] cmdline_str = ' '.join(cmdline) if (('openclaw' in cmdline_str and 'gateway' in cmdline_str) or 'openclaw.mjs' in cmdline_str or 'openclaw-gateway' in name): gateway_pid = p.info['pid'] break ``` Process-tree termination at `host-resource-watchdog.py:275-290`: ```python def kill_process_tree(self, proc): try: try: children = proc.children(recursive=True) for child in children: try: child.kill() except (psutil.NoSuchProcess, psutil.AccessDenied): pass except (psutil.NoSuchProcess, psutil.AccessDenied): pass proc.kill() except (psutil.NoSuchProcess, psutil.AccessDenied): pass ``` ### Technical Analysis The daemon scans every visible process and identifies the gateway using substring checks against process names and complete command lines. It accepts any process containing `openclaw.mjs`, any command line containing both `openclaw` and `gateway`, or any process name containing `openclaw-gateway`. These checks do not verify the executable path, UID, expected parent process, service unit, cgroup, cryptographic identity, or a trusted PID file. The loop also stops at the first matching process. After selecting a gateway, the daemon recursively monitors descendants. If a heuristic identifi ...[truncated 1746 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require an explicit gateway PID supplied by a trusted service manager or administrator-owned PID file. - Validate the process UID, executable path, creation time, cgroup, and expected parent before monitoring it. - Revalidate PID and creation time immediately before sending any signal to prevent PID-reuse issues. - Run the watchdog as a dedicated unprivileged account with permission only for the intended service. - Prefer cgroup or service-unit scoping over global process-table substring searches. - Use `SIGTERM` first, wait for a bounded grace period, and use `SIGKILL` only as a final fallback. - Require multiple independent samples and configurable thresholds before termination. - Add a monitor-only default mode and require explicit administrator opt-in for automatic killing. - Maintain an allowlist of process types that may be terminated and an exclusion list for critical workloads. - Record the verified executable identity and telemetry evidence before taking destructive action. ]]>

T08 · Insecure Dependencies

Warning
Location
deploy.sh:35
Finding
Unpinned Runtime Installation of a Third-Party Python Package<![CDATA[ ## Vulnerability Details **File Location**: `deploy.sh:35-47` **Vulnerability Type**: Unpinned dependency installation and mutable supply chain **Risk Level**: Medium ### Vulnerable Code ```bash if ! python3 -c "import psutil" &>/dev/null; then echo "[LLM Stream Guard] Installing psutil..." if command -v apt-get &>/dev/null; then apt-get update -qq && apt-get install -y -qq python3-psutil else python3 -m pip install psutil fi if ! python3 -c "import psutil" &>/dev/null; then echo "ERROR: Failed to install psutil. Aborting." exit 1 fi echo "[LLM Stream Guard] psutil installed." else ``` ### Technical Analysis When `psutil` is unavailable and `apt-get` is not present, the script installs `psutil` from the Python package index without specifying a version, hash, approved repository, or lock file. The installed artifact can therefore change over time without any modification to this Skill. Package resolution also depends on the host's pip configuration, DNS, proxy settings, and configured package indexes. A compromised or attacker-controlled index can provide code that executes during installation or is later imported by the watchdog. The `apt-get` branch likewise installs an unspecified repository-selected version, although operating-system repositories commonly provide additional signing controls. ### Attack Path 1. The deployment host does not already have an importable `psutil` package. 2. The host does not provide `apt-get`, causing the script to use pip. 3. The deployment environment resolves packages through a compromised, misconfigured, or attacker-controlled package index or proxy. 4. `python3 -m pip install psutil` downloads an unpinned artifact. 5. Installation-time behavior executes under the privileges used to run `deploy.sh`, or malicious package code executes when the watchdog imports `psutil`. 6. The dependency gains the same filesystem and process access as the installer or watchdog. ### Impact Assess ...[truncated 405 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `psutil` to a reviewed version. - Verify package hashes using a locked requirements file and `pip install --require-hashes`. - Use an administrator-approved and authenticated package repository. - Install dependencies into an isolated virtual environment rather than the system interpreter. - Separate dependency provisioning from runtime deployment and require explicit administrative approval. - Prefer prebuilt, signed deployment artifacts that include reviewed dependencies. - Record the selected package version and artifact hash in deployment logs. - Avoid automatically invoking a package manager from an application startup or integration script. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The documented purpose frames the skill as a defensive gateway guard, but the described deployment also patches application code, creates persistent background monitoring, installs dependencies, and performs host filesystem changes. This mismatch is dangerous because operators may approve it as a narrow safety feature without realizing it has code-injection, persistence, and host-modification behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose frames the skill as a defensive gateway guard, but the described deployment also patches application code, creates persistent background monitoring, installs dependencies, and performs host filesystem changes. This mismatch is dangerous because operators may approve it as a narrow safety feature without realizing it has code-injection, persistence, and host-modification behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares powerful capabilities implicitly through its behavior but does not define an explicit tool/permission scope. Because it reads environment variables, modifies files, and runs shell operations, the lack of declared scope prevents informed consent and weakens containment around a skill that can alter the host application.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill does disclose some behavior in the deployment and watchdog sections, but it does not present a prominent warning that installation modifies the gateway runtime, may kill process trees, and collects forensic system logs such as journalctl and dmesg excerpts. For a host-level defensive skill, these side effects materially affect availability and privacy, so burying them in operational details increases the chance of unsafe deployment.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The script automatically installs system or Python packages at deploy time using apt-get or pip, expanding its behavior beyond simply deploying the advertised guard logic. This creates supply-chain and change-control risk because it alters the host environment without pinning versions, isolation, or operator approval.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script installs packages without any interactive confirmation or explicit noninteractive consent flag from the operator. In an administrative deployment context, silent dependency installation can unexpectedly change the host, pull unreviewed code, and violate least surprise and approval requirements.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The deployment script appends executable code directly into the host application's main file, permanently changing application behavior outside normal extension boundaries. This is dangerous because it creates a hard-to-audit persistence point, broadens privilege to everything the app can access, and makes rollback and integrity verification more difficult.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script modifies the target application's main file by appending injected logic without explicit user confirmation. Directly rewriting a production entrypoint is a high-risk change because it can introduce persistence, break upgrades, and conceal ongoing behavior changes from operators.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The injected code starts a background watchdog daemon via execSync, pgrep, nohup, and shell command composition from inside the host application's runtime. Persistent process spawning from injected application code increases stealth, operational complexity, and abuse potential if the path or environment is manipulated.

Session Persistence

Medium
Category
Rogue Agent
Content
const distDir = new URL("dist/llm_stream_guard/", import.meta.url).pathname;
    const logDir = (process.env.HOME || "/root") + "/.openclaw/workspace/memory/core";
    execSync(`mkdir -p "${logDir}"`);
    execSync(`nohup python3 "${distDir}host-resource-watchdog.py" > "${logDir}/host_watchdog.log" 2>&1 &`);
    console.log("[Host Resource Watchdog] Started.");
  }
} catch (e) {
Confidence
94% confidence
Finding
Using nohup and backgrounding creates a persistent detached process that survives the invoking session and can continue operating without direct operator visibility. In this skill's context, that persistence is especially sensitive because it is introduced by code injected into the host application's startup path, effectively establishing a semi-hidden resident component.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The watchdog automatically kills gateway child processes based on heuristic triggers without prominently disclosing this destructive behavior. In this context, false positives could terminate legitimate workloads, disrupt service, and cause data loss or denial of service, especially because the thresholds are heuristic rather than authoritative.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The incident snapshot collects broad host-level logs from `journalctl`, `/var/log/syslog`, `/var/log/messages`, and `dmesg`, which can expose unrelated system activity, secrets, usernames, service data, and kernel diagnostics beyond the Openclaw gateway's needs. In the context of an agent skill, this expands data access significantly and creates a privacy and information-disclosure risk if snapshots are read, synced, or exfiltrated later.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The watchdog silently writes incident reports containing system and kernel logs to local files without clear disclosure or operator acknowledgment in the skill itself. This is dangerous because users may deploy the skill expecting resource monitoring, not realizing it performs forensic collection of potentially sensitive host data.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Collect system logs (journalctl -> syslog -> messages fallback)
            try:
                if shutil.which('journalctl'):
                    sys_logs = subprocess.check_output(
                        ['journalctl', '-u', 'openclaw', '-n', '50', '--no-pager'],
                        text=True, timeout=10, stderr=subprocess.DEVNULL
                    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Collect kernel segfault / OOM logs
            try:
                dmesg_result = subprocess.check_output(
                    ['dmesg', '-T'],
                    text=True, timeout=5, stderr=subprocess.DEVNULL
                )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'report_path' from os.environ.get (line 322, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
except Exception:
                dmesg_logs = 'Unable to read dmesg.'

            with open(report_path, 'w') as f:
                f.write(f'# Process Incident Report: {timestamp}\n')
                f.write(f'**Trigger**: {reason} for process `{target_process}`\n')
                if extra_info:
Confidence
95% confidence
Finding
`report_path` is derived from `OPENCLAW_WORKSPACE`, which comes from the environment, and is used for file writes without validating or constraining the resolved path. If an attacker can influence the daemon's environment or workspace path, they may redirect incident reports to arbitrary filesystem locations, potentially overwriting files or writing sensitive diagnostic content outside the intended workspace.

Tainted flow: 'AUDIT_LOG' from os.environ.get (line 39, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
f.write('## 2. Kernel Segfault/OOM Logs\n```bash\n')
                f.write(dmesg_logs + '\n```\n')

            with open(AUDIT_LOG, 'a') as f:
                f.write(f'\n\n**{timestamp} - SYSTEM CRITICAL EVENT**\n')
                f.write(f'{reason}. Snapshot saved to: `{report_path}`\n')
                if extra_info:
Confidence
95% confidence
Finding
`AUDIT_LOG` is derived from the environment-controlled workspace path and opened for append without path validation. In a privileged daemon context, this can become an arbitrary file write primitive or allow sensitive incident metadata to be redirected into attacker-chosen locations.

Tainted flow: 'AUDIT_LOG' from os.environ.get (line 39, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
breach_msg = ', '.join(redline_breached)
            print(f'[WARNING] PHYSICAL REDLINE BREACH: {breach_msg}', file=sys.stderr)
            try:
                with open(AUDIT_LOG, 'a') as f:
                    f.write(f'\n- **{timestamp}**: PHYSICAL REDLINE BREACH: {breach_msg}')
            except Exception:
                pass
Confidence
95% confidence
Finding
This is the same underlying issue as the earlier `AUDIT_LOG` write: an environment-influenced path is used for appending audit data without confinement checks. Because the watchdog may run with elevated privileges, an attacker who controls startup environment or deployment config could leverage this to write to unintended files.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
deploy.sh:81