Back to skill

Security audit

Job Execution Monitor

Security checks for vulnerabilities and agentic risk

Overview

This cron-monitoring skill is not clearly malicious, but it installs recurring user-level automation with weak validation that could be abused or repeatedly wake the agent.

Install only if you are comfortable with a user-level systemd timer or cron job that periodically runs this skill and can wake OpenClaw. Keep the workspace path and JSON config under your control, use only numeric intervals/tolerances, and prefer patching validation and explicit install confirmation before enabling it. The cleanup commands permanently remove this skill's config, state, and logs.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/install.sh:34
Finding
Unvalidated configuration is injected into persistent scheduler definitions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.sh:34-35`, `scripts/install.sh:47-70`, and `scripts/install.sh:91-100` **Vulnerability Type**: Scheduler configuration and command injection **Risk Level**: High ### Vulnerable Code ```bash # Load check interval CHECK_INTERVAL=$(jq -r '.checkIntervalMin // 10' "$CONFIG_FILE") ``` ```bash cat > "$SERVICE_FILE" <<EOF [Unit] Description=OpenClaw Job Execution Monitor healthcheck After=network.target [Service] Type=oneshot ExecStart=${HEALTHCHECK_SCRIPT} Environment="OPENCLAW_WORKSPACE=${WORKSPACE}" StandardOutput=journal StandardError=journal [Install] WantedBy=default.target EOF cat > "$TIMER_FILE" <<EOF [Unit] Description=OpenClaw Job Execution Monitor timer Requires=openclaw-job-execution-monitor.service [Timer] OnBootSec=2min OnUnitActiveSec=${CHECK_INTERVAL}min AccuracySec=1min [Install] WantedBy=timers.target EOF ``` ```bash CRON_LINE="*/${CHECK_INTERVAL} * * * * OPENCLAW_WORKSPACE=${WORKSPACE} ${HEALTHCHECK_SCRIPT} >> ${WORKSPACE}/job-execution-monitor.log 2>&1" # Check if already in crontab if crontab -l 2>/dev/null | grep -qF "$HEALTHCHECK_SCRIPT"; then echo "⚠️ Cron entry already exists" else (crontab -l 2>/dev/null; echo "$CRON_LINE") | crontab - echo "✅ Cron job added" fi ``` ### Technical Analysis `CHECK_INTERVAL` is loaded from a user-writable JSON configuration without verifying that it is an integer within a safe range. `WORKSPACE` is derived from `OPENCLAW_WORKSPACE` without rejecting newlines, quotes, whitespace, or shell metacharacters. These values are directly interpolated into systemd unit files or a crontab entry. A newline in either value can introduce additional systemd directives or cron records. In the cron fallback, shell metacharacters in the workspace value can alter the command interpreted by the cron shell. Because the generated definition is enabled and started, successful injection is persistent and executes whenever the timer or cron entry ...[truncated 1379 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `checkIntervalMin` to be a JSON number and a bounded positive integer: ```bash CHECK_INTERVAL=$(jq -er ' .checkIntervalMin // 10 | select(type == "number" and floor == . and . >= 1 and . <= 1440) ' "$CONFIG_FILE") || { echo "ERROR: checkIntervalMin must be an integer from 1 to 1440" >&2 exit 1 } ``` 2. Canonicalize the workspace path and reject control characters, newlines, carriage returns, and NUL bytes. 3. Escape values according to systemd unit-file syntax rather than inserting raw environment values into a heredoc. 4. Avoid assembling a cron shell command from configurable text. Use a fixed wrapper script whose path is controlled by the package and pass validated configuration through a protected environment file. 5. Quote all shell path uses within the generated cron command using a robust shell-escaping mechanism. 6. Generate definitions in a temporary file with restrictive permissions, validate them, and then atomically move them into place. 7. Require explicit user confirmation before enabling the persistent timer, especially when a non-default workspace or interval is used. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/healthcheck.sh:57
Finding
Untrusted values are evaluated in Bash arithmetic expressions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/healthcheck.sh:57-68`, `scripts/healthcheck.sh:80-81`, `scripts/healthcheck.sh:92-95`, and `scripts/healthcheck.sh:104-128` **Vulnerability Type**: Unsafe arithmetic evaluation of untrusted data **Risk Level**: High ### Vulnerable Code ```bash parse_cron_next() { local schedule="$1" local tolerance="${2:-600}" # Extract hour and minute (assumes "M H * * *" format) local minute=$(echo "$schedule" | awk '{print $1}') local hour=$(echo "$schedule" | awk '{print $2}') # Get today's date at specified time local today_run=$(date -d "today ${hour}:${minute}" +%s) # If we're past that time, check against today's run if (( NOW > today_run + tolerance )); then ``` ```bash schedule=$(echo "$job_config" | jq -r '.schedule // ""') tolerance=$(echo "$job_config" | jq -r '.tolerance // 600') ``` ```bash alert_key="${job_name}_missing" already_alerted=$(echo "$STATE" | jq -r --arg k "$alert_key" '.alerts[$k] // 0') if (( already_alerted == 0 )); then ``` ```bash # Extract last run time (ms) and status last_run_ms=$(echo "$job_json" | jq -r '.state.lastRunAtMs // 0') last_status=$(echo "$job_json" | jq -r '.state.lastStatus // "unknown"') last_run=$((last_run_ms / 1000)) # Convert to seconds if (( last_run == 0 )); then ``` ```bash expected_time=$(parse_cron_next "$schedule" "$tolerance") time_diff=$((NOW - last_run)) expected_diff=$((NOW - expected_time)) if (( expected_diff > tolerance && time_diff > tolerance )); then ``` ### Technical Analysis Values obtained from the editable configuration, persistent state file, and `openclaw cron list` output are used as Bash arithmetic operands without first verifying their types and formats. Bash arithmetic contexts interpret operand contents as arithmetic expressions rather than strictly converting decimal strings. Crafted expressions can cause recursive variable or array-subscript evaluation; in relevant Bash evaluation contexts, ma ...[truncated 1545 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every value before using it in arithmetic: ```bash require_uint() { local name="$1" local value="$2" if [[ ! "$value" =~ ^[0-9]+$ ]]; then echo "ERROR: $name must be an unsigned integer" >&2 exit 1 fi } ``` 2. Enforce sensible upper and lower bounds for timestamps, tolerances, intervals, hours, and minutes. 3. Require appropriate JSON types with `jq -e`, rather than accepting strings through `jq -r`. 4. Validate `lastRunAtMs` and all OpenClaw response fields against an expected schema before using them. 5. Treat a malformed state file as corrupt: move it aside, initialize a new safe state file, and emit a diagnostic message. 6. Parse cron fields with strict patterns. For the currently supported format, require exactly five fields and numeric minute/hour values in valid ranges. 7. Use explicit base-10 conversion only after validation, such as `10#$value`, to avoid alternate-base and expression interpretation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/healthcheck.sh:76
Finding
Pipeline subshell discards alert-state updates and enables repeated wake events<![CDATA[ ## Vulnerability Details **File Location**: `scripts/healthcheck.sh:76-99` and `scripts/healthcheck.sh:153-157` **Vulnerability Type**: Persistent state-management failure and alert amplification **Risk Level**: Medium ### Vulnerable Code ```bash # Check each configured job echo "$CONFIG" | jq -r '.jobs | keys[]' | while read -r job_name; do job_config=$(echo "$CONFIG" | jq -r --arg name "$job_name" '.jobs[$name]') schedule=$(echo "$job_config" | jq -r '.schedule // ""') tolerance=$(echo "$job_config" | jq -r '.tolerance // 600') if [[ -z "$schedule" ]]; then continue fi # Get job status from openclaw (redirects + json parsing) job_json=$(openclaw cron list 2>/dev/null | jq --arg name "$job_name" '.jobs[] | select(.name == $name)' 2>/dev/null || echo '{}') if [[ "$job_json" == "{}" ]]; then # Job not found alert_key="${job_name}_missing" already_alerted=$(echo "$STATE" | jq -r --arg k "$alert_key" '.alerts[$k] // 0') if (( already_alerted == 0 )); then msg="🔴 Job-Observer: Job \"$job_name\" not found in cron list" echo "$msg" send_wake "$msg" STATE=$(echo "$STATE" | jq --arg k "$alert_key" --arg t "$NOW" '.alerts[$k] = ($t | tonumber)') fi continue fi # Additional job checks omitted here; they update STATE in the same loop. done # Update state file STATE=$(echo "$STATE" | jq --arg t "$NOW" '.lastCheck = ($t | tonumber)') echo "$STATE" > "$STATE_FILE" ``` ### Technical Analysis In Bash, a `while` loop placed at the end of a pipeline normally executes in a subshell. Assignments made to `STATE` inside that subshell do not survive after the loop exits. The code therefore loses alert additions and recovery deletions performed inside the loop. The final state write only updates `lastCheck` using the parent shell's unchanged copy of `STATE`. This defeats the documented anti-spam behavior. A job that remains missing, has never run, or is considered late can cause ...[truncated 1177 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace the pipeline-fed loop with process substitution so that the loop executes in the current shell: ```bash while IFS= read -r job_name; do # Process the job and update STATE. : done < <(jq -r '.jobs | keys[]' <<<"$CONFIG") ``` Additionally: 1. Write the state file atomically using a temporary file in the same directory followed by `mv`. 2. Apply restrictive permissions, such as mode `0600`, to the state file. 3. Add locking with `flock` or an equivalent mechanism to prevent overlapping timer invocations from overwriting each other's state. 4. Add tests confirming that alert markers persist and recovery events remove them. 5. Consider a bounded alert cooldown as a secondary defense against notification and resource amplification. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (19)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
systemctl --user daemon-reload

# Optional: remove unit files
rm -f ~/.config/systemd/user/openclaw-job-execution-monitor.service \
      ~/.config/systemd/user/openclaw-job-execution-monitor.timer
```
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### Optional cleanup (config/state/log)
```bash
rm -f ~/.openclaw/workspace/job-execution-monitor.json
rm -f ~/.openclaw/workspace/.job-execution-monitor-state.json
rm -f ~/.openclaw/workspace/job-execution-monitor.log
```
Confidence
92% confidence
Finding
This command permanently deletes the skill's configuration file from the user's workspace. In a skill document, presenting destructive commands without clear warning or confirmation creates a real risk of accidental data loss, especially when users may follow instructions verbatim.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### Optional cleanup (config/state/log)
```bash
rm -f ~/.openclaw/workspace/job-execution-monitor.json
rm -f ~/.openclaw/workspace/.job-execution-monitor-state.json
rm -f ~/.openclaw/workspace/job-execution-monitor.log
```
Confidence
90% confidence
Finding
Deleting the state file removes alert history and operational state, which can interfere with recovery logic or auditability. Although the file path is fixed and not attacker-supplied, the danger comes from destructive behavior being exposed without sufficient caution in an agent-facing skill.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
rm -f ~/.openclaw/workspace/job-execution-monitor.json
rm -f ~/.openclaw/workspace/.job-execution-monitor-state.json
rm -f ~/.openclaw/workspace/job-execution-monitor.log
```

---
Confidence
89% confidence
Finding
Deleting the log file destroys troubleshooting and audit information that may be needed to investigate job failures or monitor behavior. In context this is not arbitrary command abuse, but it is still a meaningful destructive action presented without enough warning.

Ae1

High
Category
analysis-evasion
Content
- `SKILL.md` - This documentation
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Vague Triggers

Medium
Confidence
88% confidence
Finding
The invocation example "check if jobs ran" is broad natural language that could match many ordinary troubleshooting requests outside this specific cron-monitoring skill. The document does not provide narrowing constraints or negative examples to clarify when this skill should versus should not activate.

Vague Triggers

Medium
Confidence
85% confidence
Finding
The phrase "task surveillance" is uncommon and vague, giving little indication of what systems, job types, or monitoring context it refers to. Without explicit scope, it could cause unintended invocation for unrelated task-tracking or productivity requests.

Session Persistence

Medium
Category
Rogue Agent
Content
### If installed via cron
```bash
crontab -l | sed '/job-execution-monitor\/scripts\/healthcheck\.sh/d' | crontab -
```

### Optional cleanup (config/state/log)
Confidence
85% 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
### If installed via cron
```bash
crontab -l | sed '/job-execution-monitor\/scripts\/healthcheck\.sh/d' | crontab -
```

### Optional cleanup (config/state/log)
Confidence
85% 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
94% confidence
Finding
The uninstall section includes direct file-deletion commands for config, state, and logs without an explicit warning that user data and monitoring history will be permanently removed. In an agent context, users may copy or authorize these commands without understanding the data-loss consequences.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Check if running as root
if [[ $EUID -eq 0 ]]; then
  echo "ERROR: Don't run as root. User-level systemd/cron is preferred." >&2
  exit 1
fi
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Check dependencies
for cmd in jq; do
  if ! command -v "$cmd" &>/dev/null; then
    echo "ERROR: $cmd not found. Install with: sudo apt install $cmd" >&2
    exit 1
  fi
done
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
if command -v systemctl &>/dev/null && systemctl --user status &>/dev/null; then
  echo "✅ Using systemd user timer"
  
  # Create service file
  SERVICE_FILE="$HOME/.config/systemd/user/openclaw-job-execution-monitor.service"
  TIMER_FILE="$HOME/.config/systemd/user/openclaw-job-execution-monitor.timer"
Confidence
80% 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
# Reload and enable
  systemctl --user daemon-reload
  systemctl --user enable openclaw-job-execution-monitor.timer
  systemctl --user start openclaw-job-execution-monitor.timer
  
  echo "✅ Systemd timer installed and started"
Confidence
95% confidence
Finding
Enabling a systemd user timer establishes persistence by configuring the healthcheck to run automatically in the future. In an installer for a monitoring component this is likely intentional, but persistence remains security-sensitive because any compromise of the referenced script or configuration can turn the timer into a recurring execution vector.

Session Persistence

Medium
Category
Rogue Agent
Content
systemctl --user enable openclaw-job-execution-monitor.timer
  systemctl --user start openclaw-job-execution-monitor.timer
  
  echo "✅ Systemd timer installed and started"
  echo ""
  echo "Commands:"
  echo "  systemctl --user status openclaw-job-execution-monitor.timer"
Confidence
80% 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
CRON_LINE="*/${CHECK_INTERVAL} * * * * OPENCLAW_WORKSPACE=${WORKSPACE} ${HEALTHCHECK_SCRIPT} >> ${WORKSPACE}/job-execution-monitor.log 2>&1"
  
  # Check if already in crontab
  if crontab -l 2>/dev/null | grep -qF "$HEALTHCHECK_SCRIPT"; then
    echo "⚠️  Cron entry already exists"
  else
Confidence
93% confidence
Finding
The script installs a recurring per-user cron job, which creates persistence by automatically re-running the healthcheck script. Even though this appears to be intended functionality for monitoring, persistence mechanisms are security-relevant because a compromised or later-modified healthcheck script would be executed repeatedly without further user interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
if crontab -l 2>/dev/null | grep -qF "$HEALTHCHECK_SCRIPT"; then
    echo "⚠️  Cron entry already exists"
  else
    (crontab -l 2>/dev/null; echo "$CRON_LINE") | crontab -
    echo "✅ Cron job added"
  fi
Confidence
94% confidence
Finding
This line appends the persistent cron entry into the user's crontab, establishing automatic execution on a schedule. In this skill's context that is expected installer behavior, but it still materially changes the user's environment and can be abused if the referenced script or workspace path is tampered with later.

Session Persistence

Medium
Category
Rogue Agent
Content
echo ""
  echo "Commands:"
  echo "  crontab -l  # view"
  echo "  crontab -e  # edit"
  echo "  tail -f ${WORKSPACE}/job-execution-monitor.log"
fi
Confidence
85% 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.

Vague Triggers

Low
Confidence
84% confidence
Finding
This manifest-like JSON file defines a dynamic job name pattern `flight-tracker-*` and only notes that wildcards are supported, without specifying what names are in scope or excluded. Such a broad pattern can ambiguously match unintended job names, which fits the vague-trigger category for manifest files.