Back to skill

Security audit

Cron Optimizer

Security checks for vulnerabilities and agentic risk

Overview

This skill needs review because it can broadly delete scheduled jobs and automatically recreate a watchdog job without tight scoping or confirmation.

Install only if you are comfortable giving this skill authority to alter the OpenClaw cron namespace. Review the deletion rules first, avoid running it under a highly privileged account, and prefer a version with dry-run output, explicit confirmation, owner/namespace checks, safe argument-array command execution, and documented watchdog behavior.

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

T09 · Insecure Skill Coding Practices

Error
Location
index.js:88
Finding
Shell Command Injection Through Untrusted Cron Job Identifier<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 88-90 **Vulnerability Type**: OS command injection through shell interpolation **Risk Level**: High ### Vulnerable Code ```js if (shouldRemove) { try { console.log(`[CronOptimizer] Removing stale job: ${job.name} (ID: ${job.id}, Age: ${Math.floor(age/3600000)}h)`); execSync(`openclaw cron remove "${job.id}"`, { stdio: 'ignore' }); removedCount++; } catch (e) { console.error(`[CronOptimizer] Failed to remove job ${job.id}:`, e.message); } } ``` ### Technical Analysis The cron job identifier, `job.id`, is interpolated directly into a command string passed to `child_process.execSync()`. Because `execSync()` executes string commands through a shell, shell metacharacters contained in the identifier are interpreted by the shell. Wrapping the value in double quotes is insufficient. Shell features such as command substitution remain active inside double quotes, and a value containing a double quote can terminate the quoted argument and append new commands. The identifier originates from either: - The JSON output of `openclaw cron list --all --json`; or - The locally stored `evolver_cron_cache.json` cache. Neither source is structurally validated before the identifier reaches the command execution sink. ### Attack Path 1. An attacker influences the output of the OpenClaw cron listing or modifies the cache file used by the optimizer. 2. The attacker inserts a job whose identifier contains a shell payload. 3. The malicious job is configured to satisfy one of the removal conditions, such as being disabled and old enough. 4. The optimizer interpolates the malicious identifier into: ```sh openclaw cron remove "<attacker-controlled value>" ``` 5. The shell interprets the embedded payload and executes it with the privileges of the Node.js process. ### Impact Assessment Successful exploitation permits arbitrary command execution with the operat ...[truncated 378 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Avoid invoking a shell for commands containing externally derived values. Replace the string-based call with an argument-array API: ```js const { execFileSync } = require('child_process'); execFileSync( 'openclaw', ['cron', 'remove', String(job.id)], { stdio: 'ignore' } ); ``` Additionally: 1. Validate that `job.id` is a string and conforms to the exact documented OpenClaw identifier format. 2. Reject identifiers containing whitespace, control characters, quotes, or shell metacharacters. 3. Validate the full structure and types of cached cron records before using them. 4. Protect the cache file against unauthorized modification. 5. Apply the same no-shell rule to future commands that incorporate variable data. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.js:72
Finding
Unrestricted Deletion of Unrelated System-Wide Cron Jobs<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 41 and 72-90 **Vulnerability Type**: Overbroad privileged cron management and missing ownership validation **Risk Level**: High ### Vulnerable Code The optimizer retrieves every available cron job: ```js const out = execSync('openclaw cron list --all --json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000 }); const parsed = JSON.parse(out); jobs = parsed.jobs || []; ``` It then applies broad deletion rules and removes matching jobs without validating their owner or origin: ```js // Skip critical system jobs if (job.name === 'evolver_watchdog_robust' || job.name === 'Daily Auto-Update') { continue; } // Rule 1: Remove disabled jobs older than 24h if (job.enabled === false && age > STALE_AGE_MS) { shouldRemove = true; } // Rule 2: Remove completed one-shot jobs ('at') that ran successfully if (job.schedule?.kind === 'at' && job.state?.lastStatus === 'ok' && age > STALE_AGE_MS) { shouldRemove = true; } // Rule 3: Remove "Mad Dog" spam jobs (evolver loops) if disabled if ((job.name?.includes('Mad Dog') || job.payload?.message?.includes('evolver/index.js')) && job.enabled === false) { shouldRemove = true; } if (shouldRemove) { try { console.log(`[CronOptimizer] Removing stale job: ${job.name} (ID: ${job.id}, Age: ${Math.floor(age/3600000)}h)`); execSync(`openclaw cron remove "${job.id}"`, { stdio: 'ignore' }); removedCount++; } catch (e) { console.error(`[CronOptimizer] Failed to remove job ${job.id}:`, e.message); } } ``` ### Technical Analysis The use of `cron list --all` expands the operation to the complete cron namespace available to the executing account. The deletion policy does not check whether a job was created by this skill, belongs to the evolver subsystem, or is explicitly authorized for cleanup. In particular, every disabled job older than 24 hours is considered removable. A disabled jo ...[truncated 1366 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Restrict cleanup to resources demonstrably owned by this skill: 1. Add an immutable creator, owner, namespace, or managed-by marker when creating jobs. 2. Require that marker before any deletion is allowed. 3. Use an explicit allowlist of job identifiers maintained by the skill rather than scanning all cron jobs. 4. Do not treat a disabled state alone as evidence that a job is obsolete. 5. Introduce a dry-run mode that is enabled by default. 6. Require explicit confirmation before deleting jobs not conclusively owned by the optimizer. 7. Back up complete job definitions before deletion and provide a restoration procedure. 8. Apply least privilege so the skill account can manage only its own cron namespace. 9. Validate cached job data and do not allow cache contents to independently authorize deletion. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:5
Finding
Unsafe Fixed-Path State Writes Permit Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 5-6, 48, 53, and 99-107 **Vulnerability Type**: Symlink following and unsafe state-file handling **Risk Level**: Medium ### Vulnerable Code The state and cache paths are fixed and located outside the skill directory: ```js const STATE_FILE = path.resolve(__dirname, '../../memory/evolver_cron_state.json'); const CACHE_FILE = path.resolve(__dirname, '../../memory/evolver_cron_cache.json'); ``` The files are overwritten directly: ```js fs.writeFileSync(CACHE_FILE, JSON.stringify({ timestamp: Date.now(), jobs })); ``` ```js fs.writeFileSync(STATE_FILE, JSON.stringify({ lastCleanup: Date.now(), status: 'error', error: e.message })); ``` ```js fs.writeFileSync(STATE_FILE, JSON.stringify({ lastCleanup: Date.now(), status: 'ok', removed: removedCount, // Also update the watchdog check state for lifecycle.js lastChecked: Date.now(), exists: jobs.some(j => j.name === 'evolver_watchdog_robust') })); ``` ### Technical Analysis The skill writes to predictable paths without checking whether the path is a symbolic link, verifying file ownership, setting an explicit restrictive mode, or using a no-follow file-opening option. `fs.writeFileSync()` follows symbolic links by default and truncates the resolved target. If another local user or process can create or replace one of these predictable paths, it can redirect the optimizer’s write operation to another file writable by the optimizer process. The use of direct, non-atomic writes also allows partial state files if the process is interrupted during a write. ### Attack Path 1. An attacker with write access to the relevant `memory` directory removes or replaces a state or cache file. 2. The attacker creates a symbolic link at the predictable path pointing to another file. 3. The optimizer runs under an account that can write to the symlink target. 4. `fs.writeFileSync()` follows the link and truncates or overwrites the ...[truncated 704 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a private state directory owned exclusively by the skill’s execution account and apply restrictive permissions. Before writing: 1. Inspect the destination with `fs.lstatSync()` and reject symbolic links. 2. Open files using `O_NOFOLLOW` where the operating system supports it. 3. Use exclusive or otherwise carefully selected open flags to prevent path substitution. 4. Create state files with mode `0600` and the containing directory with mode `0700`. 5. Verify the owner and permissions of existing files before updating them. 6. Write to a securely created temporary file in the same protected directory, flush it, and atomically rename it over the destination. 7. Ensure less-privileged users cannot rename or replace entries in the containing directory. 8. Validate the state and cache schema when reading these files. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose is limited to pruning stale or redundant cron entries, but the finding indicates additional persistence-oriented behavior: checking for a watchdog, recreating it by executing another script, and maintaining state files. That mismatch is dangerous because operators may approve or run the skill expecting cleanup only, while it can also restore scheduled execution paths and preserve persistence mechanisms not disclosed in the interface.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill’s stated purpose is cron cleanup, but it also provisions a separate watchdog job when absent. This hidden persistence behavior expands scope beyond maintenance and can re-establish scheduled execution without operator approval, which is dangerous in an agent skill because it can preserve or restore autonomous execution after cleanup attempts.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
Invoking an unrelated lifecycle script from a cron cleanup tool creates an undeclared control path that can modify scheduler state outside the advertised function. This is risky because it chains trust to another script and recreates persistence automatically, making review and operator intent harder to verify.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill advertises removal of cron jobs and stale scheduled tasks but provides no warning that it makes destructive system changes. In a system-management context, silent deletion of scheduled jobs can disable legitimate automation, monitoring, backups, or recovery tasks, especially if the cleanup criteria are imperfect or overly broad.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The docstring presents the skill as a cleanup-only utility, but the implementation also restores a missing watchdog job. This mismatch is security-relevant because it conceals persistence-affecting behavior from reviewers and users, reducing informed consent and increasing the chance the skill is deployed with misunderstood privileges.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill automatically deletes cron jobs based on heuristic rules without confirmation, approval workflow, or dry-run mode. In a scheduler-management context, this can remove legitimate jobs, disrupt automation, and create availability issues if metadata is stale, incomplete, or attacker-influenced.

Missing User Warnings

Low
Confidence
91% confidence
Finding
Recreating a watchdog job without prior warning or consent is an unauthorized state-changing action, even if intended for resilience. In this skill context it is more dangerous because it is bundled into a cleanup tool, so operators may run it expecting only deletions while it silently reintroduces scheduled execution.