Back to skill

Security audit

Ops Hygiene

Security checks for vulnerabilities and agentic risk

Overview

This maintenance skill is not clearly malicious, but it mixes routine health checks with automatic email/API-key use and broad host/workspace inspection that merits Review before installation.

Install only if you want an agent-maintenance skill that can run shell scripts, inspect the local OpenClaw workspace and parts of the host, write maintenance state/memory, and use local services. Before enabling recurring heartbeat use, remove or configure the hard-coded AgentMail inbox, require explicit consent for using secrets from .secrets, restrict auditing to approved paths, and treat the bundled security-audit results as advisory until the script bugs are fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/heartbeat-dispatch.sh:35
Finding
Python Code Injection Through Unescaped Filesystem Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/heartbeat-dispatch.sh:35-64`; `scripts/security-audit.sh:79-87` **Vulnerability Type**: Python source injection through shell-variable interpolation **Risk Level**: High ### Vulnerable Code ```bash get_last_check() { local key="$1" if [ -f "$STATE_FILE" ]; then python3 -c " import json with open('$STATE_FILE') as f: d = json.load(f) v = d.get('lastChecks', {}).get('$key', 0) print(v if isinstance(v, (int, float)) and v is not None else 0) " 2>/dev/null || echo "0" else echo "0" fi } update_state() { local key="$1" python3 -c " import json, os path = '$STATE_FILE' os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path) as f: d = json.load(f) except: d = {'lastChecks': {}} d.setdefault('lastChecks', {})['$key'] = $NOW with open(path, 'w') as f: json.dump(d, f, indent=2) " 2>/dev/null } ``` ```bash CONFIG="$HOME/.openclaw/openclaw.json" if [ -f "$CONFIG" ]; then # Check if elevated commands are restricted ELEVATED=$(python3 -c " import json with open('$CONFIG') as f: c = json.load(f) sec = c.get('security', {}) elevated = sec.get('elevated', 'unknown') print(f'elevated={elevated}') " 2>/dev/null || echo "error reading config") ``` ### Technical Analysis Paths derived from the shell environment are embedded directly into Python source code passed to `python3 -c`. In particular, `STATE_FILE` and `CONFIG` are derived from the value of `HOME`. Shell expansion does not sanitize quotes contained in variable values. If a path contains a single quote, that quote becomes part of the generated Python program and can terminate the Python string literal. An attacker who controls the execution environment can append Python statements and comment out the remaining generated source. The `key` argument is interpolated using the same unsafe technique. Current call sites use fixed internal key names, which reduces immedia ...[truncated 1466 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not interpolate shell values into Python source code. Pass every path, timestamp, and key as a positional argument or environment variable. For example: ```bash python3 - "$STATE_FILE" "$key" "$NOW" <<'PY' import json import os import sys path = sys.argv[1] key = sys.argv[2] now = int(sys.argv[3]) os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path, encoding="utf-8") as handle: data = json.load(handle) except (FileNotFoundError, json.JSONDecodeError, OSError): data = {"lastChecks": {}} data.setdefault("lastChecks", {})[key] = now with open(path, "w", encoding="utf-8") as handle: json.dump(data, handle, indent=2) PY ``` Apply the same pattern to `CONFIG`. Additional hardening should include: 1. Validate that resolved paths remain under the expected OpenClaw directory. 2. Reject unexpected control characters in relevant environment variables. 3. Avoid broad `except:` clauses; catch expected exceptions explicitly. 4. Run scheduled maintenance under a dedicated, minimally privileged account. 5. Add tests using paths containing quotes, spaces, newlines, and shell metacharacters. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/security-audit.sh:20
Finding
Security Audit Reports Detected Secrets as Clean<![CDATA[ ## Vulnerability Details **File Location**: `scripts/security-audit.sh:20-25` **Vulnerability Type**: Incorrect pipeline status handling and security-check bypass **Risk Level**: Medium ### Vulnerable Code ```bash # 1. Secret scan log "▸ 1. SECRET SCAN" if bash "$(dirname "$0")/secret-scan.sh" "$WORKSPACE" 2>&1 | grep -q "🔴"; then log " ⚠️ SECRETS FOUND — see secret-scan output" ISSUES=$((ISSUES + 1)) else log " ✅ No secrets in workspace" fi ``` The invoked scanner explicitly returns status 1 when it detects secrets: ```bash if [ "$FOUND" -eq 1 ]; then echo "🔴 Secrets detected — review and clean up!" exit 1 else echo "✅ No secrets found — workspace is clean" exit 0 fi ``` ### Technical Analysis The audit enables `set -euo pipefail`. With `pipefail`, a pipeline returns a failure status when any component fails, rather than returning only the status of its final command. `secret-scan.sh` intentionally exits with status 1 when it detects a secret. Consequently, even when `grep -q "🔴"` finds the expected marker, the pipeline remains unsuccessful because the scanner returned 1. The `if` condition therefore selects the `else` branch and prints `No secrets in workspace`. Using human-readable output and an emoji as the machine-readable security decision also makes the integration brittle. Changes in output wording, encoding, or locale can produce additional false results. ### Attack Path 1. A credential matching one of the scanner patterns is stored in the workspace. 2. The monthly security audit invokes `secret-scan.sh`. 3. The scanner correctly detects the credential, prints the red warning marker, and exits with status 1. 4. Because `pipefail` is active, the complete pipeline returns a nonzero status. 5. The audit enters the `else` branch. 6. The audit reports that no secrets exist and does not increment `ISSUES`. 7. An operator relying on the summary may leave the exposed credential active and present in the wo ...[truncated 477 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Capture the scanner's output and exit status separately. Define an explicit exit-code contract, such as: - `0`: no findings - `1`: findings detected - `2` or greater: scanner error For example: ```bash set +e SCAN_OUTPUT=$(bash "$(dirname "$0")/secret-scan.sh" "$WORKSPACE" 2>&1) SCAN_STATUS=$? set -e case "$SCAN_STATUS" in 0) log " ✅ No secrets in workspace" ;; 1) log " ⚠️ SECRETS FOUND — review scanner output" ISSUES=$((ISSUES + 1)) ;; *) log " 🔴 Secret scanner failed with status $SCAN_STATUS" ISSUES=$((ISSUES + 1)) ;; esac ``` Further hardening should include: 1. Do not parse decorative text or emojis to determine security status. 2. Provide a structured output mode, such as JSON, if detailed results are needed. 3. Distinguish scanner failure from a clean result. 4. Add regression tests for clean scans, detected secrets, unreadable directories, and scanner crashes. 5. Ensure the audit summary returns a nonzero status for both findings and incomplete scans. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/secret-scan.sh:46
Finding
Secret Scanner Excludes Files Likely to Contain Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/secret-scan.sh:46-54` **Vulnerability Type**: Incomplete secret-scanning coverage **Risk Level**: Medium ### Vulnerable Code ```bash # Files to skip EXCLUDE="--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=__pycache__ --exclude=*.jsonl --exclude=*.log --exclude=secret-scan.sh --exclude=auth-profiles.json" for i in "${!PATTERNS[@]}"; do pattern="${PATTERNS[$i]}" desc="${DESCRIPTIONS[$i]}" # Search, suppressing errors matches=$(grep -rn $EXCLUDE -E "$pattern" "$TARGET" 2>/dev/null || true) ``` The later filename check does not compensate for all excluded file types: ```bash sensitive_files=$(find "$TARGET" -maxdepth 4 \ \( -name ".env" -o -name ".env.local" -o -name "*.pem" -o -name "*.key" \ -o -name "id_rsa" -o -name "id_ed25519" -o -name "credentials.json" \) \ -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null || true) ``` ### Technical Analysis The content scan excludes all `.log` and `.jsonl` files as well as `auth-profiles.json`. Logs and transcript-like JSONL files commonly receive credentials through diagnostic output, request serialization, copied command lines, exception messages, or agent conversations. A file named `auth-profiles.json` is also intrinsically likely to contain authentication material. The scanner subsequently checks only a fixed set of sensitive filenames. That check does not include arbitrary `.log` or `.jsonl` files or `auth-profiles.json`, so credentials in those locations can produce a clean result. The scanner also suppresses all `grep` and `find` errors. Permission failures or malformed targets can therefore reduce coverage without causing an incomplete-scan warning. ### Attack Path 1. A credential is accidentally written to a `.log`, `.jsonl`, or `auth-profiles.json` file. 2. The scanner applies its global exclusion list. 3. No content inspection is performed on the affected file. 4. The secondary ...[truncated 772 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove blanket exclusions for `.log`, `.jsonl`, and authentication-profile files. Scan their content while ensuring that output contains only file paths, line numbers, and secret types—not secret values. If performance requires exclusions: 1. Report every excluded path category as an explicit coverage gap. 2. Add a separate high-risk scan for log, transcript, and authentication files. 3. Limit scanning by file size rather than excluding complete file types. 4. Add `auth-profiles.json` to the sensitive-filename check at minimum. 5. Return a distinct incomplete-scan status when files cannot be read. 6. Replace the string-based `EXCLUDE` variable with a Bash array so each option is passed as a separate argument. 7. Add entropy-based and generic credential-assignment detection to supplement vendor-specific patterns. 8. Include regression tests placing representative tokens in each currently excluded file type. ]]>

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/security-audit.sh:27
Finding
Maintenance Scripts Perform Broad Host Reconnaissance Beyond a Minimal Heartbeat<![CDATA[ ## Vulnerability Details **File Location**: `scripts/security-audit.sh:27-132`; `scripts/health-check.sh:9-65` **Vulnerability Type**: Excessive host information collection and least-privilege exposure **Risk Level**: Low ### Vulnerable Code ```bash # 2. File permissions log "▸ 2. FILE PERMISSIONS" WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do if [ -f "$f" ]; then perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null) if [ "${perms: -1}" != "0" ]; then echo "$f ($perms)" fi fi done) ``` ```bash # 4. OpenClaw config review log "▸ 4. OPENCLAW CONFIG" CONFIG="$HOME/.openclaw/openclaw.json" ``` ```bash # 5. Open ports log "▸ 5. NETWORK EXPOSURE" LISTENING=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -v "^COMMAND" | awk '{print $1, $9}' | sort -u || echo "none") ``` ```bash # 7. Stale sessions log "▸ 7. STALE PROCESSES" STALE=$(ps aux | grep -E "[n]ode server|[p]ython.*serve" | grep -v grep || true) ``` The health check also enumerates broad process information: ```bash # Stale processes BG_COUNT=$(ps aux | grep -c "[n]ode\|[p]ython3\|[o]llama" || echo 0) echo " \"backgroundProcesses\": $BG_COUNT," ``` ### Technical Analysis The declared comprehensive monthly security audit reasonably requires some host inspection. However, the implementation enumerates system-wide process details, all listening TCP services, OpenClaw security configuration, and sensitive filenames. The health check also uses `ps aux`, which can expose information about processes owned by users other than the Skill's account. These commands do not elevate privileges and no external exfiltration path was found. The risk arises because the resulting reports provide useful reconnaissance if they are exposed to an untrusted caller, retained in insecure logs, or passed to a compromised downstream component. The broad monthly audit ...[truncated 1383 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Separate the functionality into two explicit modes: 1. A minimal recurring heartbeat that reports only threshold-based disk, memory, and service availability. 2. An opt-in full security audit that performs process, port, configuration, and sensitive-file inspection. Additional hardening should include: - Scope process enumeration to the current user where supported. - Report counts or threshold violations instead of complete process and service details. - Avoid printing absolute paths to sensitive files unless explicitly requested. - Redact configuration values and report only validated security states. - Protect audit output with restrictive file permissions if it is persisted. - Require explicit operator authorization before running broad host reconnaissance. - Document every collected data category and its operational purpose. - Run the scripts under a dedicated account with only the workspace and service visibility required for maintenance. ]]>
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The stated purpose suggests ordinary housekeeping, but the content expands into email access, message triage, use of external or local LLM services, secret-dependent resource access, and autonomous escalation behavior. This mismatch is dangerous because users or orchestrators may grant or invoke the skill under a lower-risk assumption, while it actually processes sensitive communications and can trigger broader actions than expected.

External Script Fetching

High
Category
Supply Chain
Content
echo "  \"cpu\": {\"load1m\": $LOAD, \"cores\": $CPU_COUNT},"

# Ollama status
if curl -s --max-time 2 http://localhost:11434/api/tags > /dev/null 2>&1; then
    MODEL_COUNT=$(curl -s http://localhost:11434/api/tags | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('models',[])))" 2>/dev/null || echo 0)
    LOADED=$(curl -s http://localhost:11434/api/ps | python3 -c "import sys,json; models=json.load(sys.stdin).get('models',[]); print(models[0]['name'] if models else 'none')" 2>/dev/null || echo "unknown")
    echo "  \"ollama\": {\"running\": true, \"models\": $MODEL_COUNT, \"loaded\": \"$LOADED\"},"
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
# Ollama status
if curl -s --max-time 2 http://localhost:11434/api/tags > /dev/null 2>&1; then
    MODEL_COUNT=$(curl -s http://localhost:11434/api/tags | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('models',[])))" 2>/dev/null || echo 0)
    LOADED=$(curl -s http://localhost:11434/api/ps | python3 -c "import sys,json; models=json.load(sys.stdin).get('models',[]); print(models[0]['name'] if models else 'none')" 2>/dev/null || echo "unknown")
    echo "  \"ollama\": {\"running\": true, \"models\": $MODEL_COUNT, \"loaded\": \"$LOADED\"},"
else
    echo "  \"ollama\": {\"running\": false},"
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
reef_triage() {
    local prompt="$1"
    local response
    response=$(curl -s --max-time 10 -X POST "${REEF_URL}/api/delegate" \
        -H "Content-Type: application/json" \
        -d "{\"prompt\": \"$prompt\"}" 2>/dev/null)
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Credential Access

High
Category
Privilege Escalation
Content
# Check for common secret file patterns
echo "📁 Checking for sensitive files..."
sensitive_files=$(find "$TARGET" -maxdepth 4 \
    \( -name ".env" -o -name ".env.local" -o -name "*.pem" -o -name "*.key" \
    -o -name "id_rsa" -o -name "id_ed25519" -o -name "credentials.json" \) \
    -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null || true)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
echo "📁 Checking for sensitive files..."
sensitive_files=$(find "$TARGET" -maxdepth 4 \
    \( -name ".env" -o -name ".env.local" -o -name "*.pem" -o -name "*.key" \
    -o -name "id_rsa" -o -name "id_ed25519" -o -name "credentials.json" \) \
    -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null || true)

if [ -n "$sensitive_files" ]; then
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# 2. File permissions
log "▸ 2. FILE PERMISSIONS"
WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do
    if [ -f "$f" ]; then
        perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null)
        if [ "${perms: -1}" != "0" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# 2. File permissions
log "▸ 2. FILE PERMISSIONS"
WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do
    if [ -f "$f" ]; then
        perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null)
        if [ "${perms: -1}" != "0" ]; then
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to run shell scripts and update files such as memory logs and state JSON, but it does not declare any explicit tool scope or permissions boundaries. That makes the effective authority ambiguous and increases the chance the agent will execute file-write and shell actions more broadly than intended, especially because the skill is framed as routine maintenance and may be invoked frequently.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The activation language is very broad, covering periodic checks, security audits, memory maintenance, secret rotation, dependency updates, housekeeping, scheduling, and security posture questions. In context, that breadth is more dangerous because the skill also contains operational shell commands, file writes, and monitoring/escalation workflows, making over-invocation likely in situations where a narrower, less privileged skill should be used.

External Transmission

Medium
Category
Data Exfiltration
Content
reef_triage() {
    local prompt="$1"
    local response
    response=$(curl -s --max-time 10 -X POST "${REEF_URL}/api/delegate" \
        -H "Content-Type: application/json" \
        -d "{\"prompt\": \"$prompt\"}" 2>/dev/null)
Confidence
77% confidence
Finding
This performs network transmission to a separate service boundary, which is security-relevant because the script is intended for routine maintenance and runs unattended. While the destination is localhost, that still exposes data to another process and expands the attack surface if the local service is compromised or spoofed.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script sends operational data to a local HTTP service without authentication, encryption, or meaningful disclosure. Even if the prompt currently contains only a message count, localhost services can be replaced, proxied, or monitored by other local processes, making this an avoidable data-flow and trust-boundary issue.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
This maintenance script accesses a real email inbox and uses a secret-derived API key, which exceeds the minimally necessary scope implied by a housekeeping/heartbeat dispatcher. Even though it only counts messages, inbox access introduces unnecessary privilege and creates risk of unintended data exposure, account misuse, or future expansion into broader mailbox processing.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script extracts an API key from a workspace secrets file and uses it automatically for mailbox access without explicit disclosure or consent at runtime. Silent use of stored secrets increases the chance of unauthorized account access, accidental privilege creep, and operational abuse if the workspace or script is tampered with.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
# 2. File permissions
log "▸ 2. FILE PERMISSIONS"
WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do
    if [ -f "$f" ]; then
        perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null)
        if [ "${perms: -1}" != "0" ]; then
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
# 2. File permissions
log "▸ 2. FILE PERMISSIONS"
WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do
    if [ -f "$f" ]; then
        perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null)
        if [ "${perms: -1}" != "0" ]; then
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
# 2. File permissions
log "▸ 2. FILE PERMISSIONS"
WORLD_READABLE=$(find "$WORKSPACE" -maxdepth 3 -name ".secrets" -o -name ".env" -o -name "*.key" 2>/dev/null | while read -r f; do
    if [ -f "$f" ]; then
        perms=$(stat -f "%Lp" "$f" 2>/dev/null || stat -c "%a" "$f" 2>/dev/null)
        if [ "${perms: -1}" != "0" ]; then
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
done)

if [ -n "$WORLD_READABLE" ]; then
    log "  ⚠️  World-readable sensitive files:"
    echo "$WORLD_READABLE" | while read -r f; do log "     $f"; done
    ISSUES=$((ISSUES + 1))
else
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script audits both the declared workspace and an unrelated hard-coded directory under $HOME/projects/bot-network, which expands its inspection scope beyond what the skill metadata describes. In an agent-maintenance context, this can disclose package/security posture for external projects and violates least-astonishment and least-privilege expectations.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
The section header and inline comment describe 'Email Triage' and ask Reef whether any email is urgent, implying message-level assessment. In reality, the code only fetches the number of messages and sends Reef a prompt containing that count, so urgency cannot be determined from the actual emails.

Static analysis

No suspicious patterns detected.