Back to skill

Security audit

Device Heartbeat Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real heartbeat monitor, but it installs a persistent background job that can repeatedly contact any supplied URL and handles monitoring secrets in exposed ways.

Install only if you intentionally want a macOS LaunchAgent that keeps running after login and periodically sends heartbeat requests. Use only a valid Healthchecks.io ping URL, avoid putting API keys in shell history, and review or rotate the ping URL/API key if they appear in logs, chat, or command transcripts.

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

Warning
Location
scripts/setup.sh:7
Finding
Persistent Arbitrary Network Requests Through an Unvalidated Heartbeat URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:7-53`; `scripts/heartbeat.sh:5-56` **Vulnerability Type**: Unvalidated network destination resulting in persistent blind SSRF behavior **Risk Level**: Medium ### Complete Code Snippet From `scripts/setup.sh`: ```bash PING_URL="${1:?Usage: setup.sh <PING_URL> [INTERVAL_SECONDS]}" INTERVAL="${2:-180}" LABEL="ai.openclaw.device-heartbeat" PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" HEARTBEAT_SCRIPT="${SCRIPT_DIR}/heartbeat.sh" cat > "$PLIST_PATH" << EOF <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${LABEL}</string> <key>ProgramArguments</key> <array> <string>/bin/bash</string> <string>${HEARTBEAT_SCRIPT}</string> <string>${PING_URL}</string> <string>${INTERVAL}</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> </dict> </plist> EOF launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" ``` From `scripts/heartbeat.sh`: ```bash PING_URL="${1:?Usage: heartbeat.sh <PING_URL> [INTERVAL_SECONDS]}" INTERVAL="${2:-180}" while true; do HTTP_CODE=$(curl -fsS --retry 2 --max-time 10 -o /dev/null -w "%{http_code}" "$PING_URL" 2>/dev/null) if [ "$HTTP_CODE" = "200" ]; then FAIL_COUNT=0 update_state "up" else FAIL_COUNT=$((FAIL_COUNT + 1)) update_state "down" fi sleep "$INTERVAL" done ``` ### Technical Analysis The Skill's declared purpose requires periodic requests to a Healthchecks.io ping endpoint, so installing a user-level LaunchAgent and sending outbound heartbeats are functionally justified. However, `PING_URL` is accepted without validating its scheme, hostname, port, path, or UUID format. The unvalidated value is written into a persistent LaunchAgent and repeatedly passed t ...[truncated 2063 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the supplied URL before creating the LaunchAgent. 2. Require the `https` scheme. 3. Allowlist the exact expected hostname, such as `hc-ping.com`. 4. Reject embedded credentials, unexpected ports, fragments, and malformed paths. 5. Validate the check identifier against the expected Healthchecks.io UUID format. 6. Resolve and reject loopback, private, link-local, multicast, and otherwise non-public destinations where practical. 7. Construct the complete endpoint internally from a validated UUID rather than accepting an arbitrary full URL. 8. Validate `INTERVAL` as a bounded positive integer to prevent unintended rapid request loops. 9. Fail closed and do not install or bootstrap the LaunchAgent when validation fails. For example, the preferred interface should accept only a UUID: ```bash CHECK_UUID="${1:?Usage: setup.sh <CHECK_UUID> [INTERVAL_SECONDS]}" if ! [[ "$CHECK_UUID" =~ ^[0-9a-fA-F-]{32,36}$ ]]; then echo "Invalid Healthchecks.io check UUID" >&2 exit 1 fi PING_URL="https://hc-ping.com/${CHECK_UUID}" ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.sh:21
Finding
Capability URL and API Credentials Are Exposed Through Plaintext Configuration and Process Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:21-34,55-59`; `scripts/check.sh:6-18` **Vulnerability Type**: Insecure handling of sensitive URLs and API credentials **Risk Level**: Medium ### Complete Code Snippet From `scripts/setup.sh`: ```bash cat > "$PLIST_PATH" << EOF <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${LABEL}</string> <key>ProgramArguments</key> <array> <string>/bin/bash</string> <string>${HEARTBEAT_SCRIPT}</string> <string>${PING_URL}</string> <string>${INTERVAL}</string> </array> </dict> </plist> EOF launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" echo "✅ Heartbeat service installed and started" echo " Plist: $PLIST_PATH" echo " Log: $LOG_DIR/heartbeat.log" echo " URL: ${PING_URL:0:40}..." echo " Interval: ${INTERVAL}s" ``` From `scripts/check.sh`: ```bash API_KEY="${1:?Usage: check.sh <API_KEY> [CHECK_UUID]}" CHECK_UUID="$2" if [ -n "$CHECK_UUID" ]; then curl -fsS --max-time 10 \ -H "X-Api-Key: $API_KEY" \ "https://healthchecks.io/api/v3/checks/${CHECK_UUID}" 2>/dev/null else curl -fsS --max-time 10 \ -H "X-Api-Key: $API_KEY" \ "https://healthchecks.io/api/v3/checks/" 2>/dev/null fi ``` ### Technical Analysis The project documentation correctly identifies the full Healthchecks.io ping URL as a secret-like capability: possession of the URL permits forged heartbeat events. Despite that warning, `setup.sh` stores the complete URL as a plaintext `ProgramArguments` value in the LaunchAgent plist. The URL is also partially printed after installation. Depending on URL length and format, the first 40 characters may disclose all or a substantial portion of the capability-bearing identifier. Output may be retained in terminal transcripts, automation logs, support records, or chat mes ...[truncated 2396 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the ping capability in macOS Keychain and have the heartbeat process retrieve it at runtime. 2. If Keychain integration is not feasible, use a dedicated secret file with permissions set to `0600`, and store only the file path in the LaunchAgent plist. 3. Do not print any portion of the ping URL during setup. Print only a non-sensitive hash or a redacted identifier that cannot reconstruct the capability. 4. Avoid accepting API keys as positional command-line arguments. 5. Read the API key from protected standard input, macOS Keychain, or a mode-`0600` file. 6. Avoid placing the API key directly in `curl` arguments where feasible. A protected temporary curl configuration or header file can reduce process-list exposure, but it must be created securely and removed reliably. 7. Continue recommending read-only, least-privilege API keys and document rotation procedures. 8. Set restrictive permissions explicitly after creating sensitive files: ```bash umask 077 mkdir -p "$LOG_DIR" chmod 700 "$LOG_DIR" chmod 600 "$PLIST_PATH" ``` 9. Instruct users to rotate the ping UUID and API key if either has appeared in command history, logs, chat messages, or support output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (30)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The same skill description also omits that it can uninstall a local service, stop a LaunchAgent, and delete files from the user's LaunchAgents directory. Concealing destructive or administrative actions inside a monitoring-themed skill raises the risk of unintended service disruption or data loss if triggered by a casual request.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The same skill description also omits that it can uninstall a local service, stop a LaunchAgent, and delete files from the user's LaunchAgents directory. Concealing destructive or administrative actions inside a monitoring-themed skill raises the risk of unintended service disruption or data loss if triggered by a casual request.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises shell-based setup, status, check, and uninstall commands but does not declare any tool restrictions or allowed-tools scope. In an agent environment, missing explicit tool scoping increases the chance the skill can invoke shell actions more broadly than intended, including modifying local services and files.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The activation phrases are broad enough to match common requests like 'is my Mac online' or generic connectivity questions, which could cause the skill to trigger in contexts where the user did not intend local monitoring setup or local shell inspection. Overbroad routing increases the likelihood of inappropriate execution of shell-backed actions.

Session Persistence

Medium
Category
Rogue Agent
Content
## Setup

### 1. Create a Check on healthchecks.io

Register at https://healthchecks.io, create a Check. Set Period = 3 min, Grace = 5 min. See `references/healthchecks-setup.md` for details.
Confidence
78% confidence
Finding
The skill sets up persistent background monitoring via a LaunchAgent and stores ongoing state/log data under ~/.openclaw/logs. Persistence itself is part of the feature, but in an agent setting it introduces ongoing execution and retained state that may outlive the user's immediate request and could be abused or misunderstood.

Session Persistence

Medium
Category
Rogue Agent
Content
# 停止已有的服务
launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null || true

cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Confidence
91% confidence
Finding
Beginning the heredoc that writes the LaunchAgent plist is part of the mechanism that creates login persistence for the user. In this skill, persistent monitoring is functionally related to device heartbeat checks, but it is still sensitive because it installs autorun behavior that may outlive user awareness or consent.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script silently writes a LaunchAgent plist into ~/Library/LaunchAgents and bootstraps it immediately, creating persistence at user login without any interactive warning or confirmation. In a skill context, this is security-relevant because it changes startup behavior and could surprise users or be abused to maintain ongoing execution beyond the original task.

Session Persistence

Medium
Category
Rogue Agent
Content
cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
Confidence
90% confidence
Finding
This duplicate detection corresponds to the same LaunchAgent plist creation and reflects real session persistence. The danger is not from the plist format itself, but from silently establishing recurring execution in the user's account.

Session Persistence

Medium
Category
Rogue Agent
Content
cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
Confidence
90% confidence
Finding
This duplicate detection corresponds to the same LaunchAgent plist creation and reflects real session persistence. The danger is not from the plist format itself, but from silently establishing recurring execution in the user's account.

Session Persistence

Medium
Category
Rogue Agent
Content
cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${LABEL}</string>
Confidence
89% confidence
Finding
The XML plist body at this location is part of the persisted LaunchAgent definition that will cause the heartbeat script to run automatically. Given the skill's stated purpose, the behavior is contextually expected, but still security-relevant because it modifies session startup behavior.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The generated LaunchAgent stores a user-supplied URL and runs a background heartbeat script against it on an ongoing basis, but the setup flow does not explicitly warn that repeated network requests will continue after installation. In a remote-device monitoring skill, this behavior is expected, but persistence plus silent outbound traffic still creates privacy and transparency risks if users do not realize monitoring remains active.

Session Persistence

Medium
Category
Rogue Agent
Content
<key>Nice</key>
    <integer>10</integer>
</dict>
</plist>
EOF

launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"
Confidence
93% confidence
Finding
The launchctl bootstrap command activates the written LaunchAgent, making persistence effective immediately for the current user session. This is the key enforcement step for autorun behavior and is dangerous if performed without informed consent, especially in agent skills that may be invoked casually.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/bin/bash
# 卸载心跳服务
LABEL="ai.openclaw.device-heartbeat"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
echo "Done. Heartbeat service uninstalled."
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service was not running"
rm -f "$PLIST_PATH" && echo "✅ Plist removed: $PLIST_PATH" || echo "⚠️ Plist not found"
echo "Done. Heartbeat service uninstalled."
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script performs a file deletion with `rm -f` on the user's LaunchAgents plist as part of uninstalling the heartbeat service. Although it logs the action, there is no confirmation prompt or prior warning before executing this destructive step, and shell deletion is safety-relevant for code files.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The file’s natural-language instructions are entirely Chinese, which can amount to a language/locale policy issue when no user opt-in or alternative language option is provided. The stated policy specifically calls for flagging skills that force a specific language without user choice.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The script's natural-language guidance and comments are presented only in Chinese, which can impose a language requirement on users without opt-in. Under the language/locale policy, this is a natural-language policy concern unless the language constraint is explicitly justified or alternatives are offered.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This code performs network requests to healthchecks.io and includes the provided API key in the X-Api-Key header. While comments note that an API key is required, there is no runtime disclosure, confirmation, or explicit user-facing warning that the credential will be sent over the network.

Static analysis

No suspicious patterns detected.