Back to skill

Security audit

Dial A Cron

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed cron automation tool, but it gives job configs broad command, file, and network power without strong built-in limits.

Install only if you will run it in a least-privilege, isolated environment and personally control every job config. Treat diffs, commands, routes, webhook URLs, file paths, and output as sensitive; avoid secrets, internal metadata services, arbitrary shell strings, and untrusted recipients.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/router.py:85
Finding

Shell Command Injection in Telegram, A2A, and Email Delivery

Content
View full analysis
Optional[str]: """Deliver via OpenClaw message tool (shell out to openclaw CLI).""" target = spec.target_id or spec.to try: cmd = f'openclaw message send --to "{target}" --message "{message[:4000]}"' result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) if result.returncode != 0: return f"Telegram delivery failed: {result.stderr[:200]}" return None except Exception as e: return f"Telegram delivery error: {e}" ``` ```python def _deliver_a2a(self, spec: RoutingSpec, message: str) -> Optional[str]: """Send via A2A (shell out).""" agent = spec.target_id or spec.to try: cmd = f'openclaw a2a send --to "{agent}" --message "{message[:2000]}"' result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) if result.returncode != 0: return f"A2A delivery failed: {result.stderr[:200]}" return None except Exception as e: return f"A2A delivery error: {e}" ``` ```python def _deliver_email(self, spec: RoutingSpec, severity: str, summary: str, body: str) -> Optional[str]: """Send email via gog CLI.""" to_addr = spec.target_id if not to_addr: return "Email: no target_id (address) specified" try: subject = f"[Dial-a-Cron] [{severity.upper()}] {self.job_id}" full_body = f"{summary}\n\n{body[:4000]}" if body != summary else summary cmd = f'gog gmail send --to "{to_addr}" --subject "{subject}" --body "{full_body[:4000]}"' result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) if result.return ...[truncated 2135 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/diff.py:108
Finding

Unrestricted Shell Command Execution Through Command Diff Specifications

Content
View full analysis
Optional[dict]: """Diff command stdout by hash.""" cmd = spec["cmd"] label = spec.get("label", cmd[:40]) try: result = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=15 ) output = result.stdout.strip() h = _hash(output) prev = prev_hashes.get(cmd) if h != prev: return {"type": "command", "label": label, "change": f"output changed", "hash": h, "cmd": cmd, "snippet": output[:200]} return None except subprocess.TimeoutExpired: return {"type": "command", "label": label, "change": "command timed out"} except Exception as e: return {"type": "command", "label": label, "change": f"error: {e}"} ``` ### Technical Analysis The `cmd` field is loaded from a job configuration and executed directly through the system shell. There is no command allowlist, executable validation, argument separation, sandbox, or privilege restriction enforced by the code. Although command-based diffing is documented functionality, accepting an unrestricted shell program exceeds the minimum capability needed to compare the output of known monitoring commands. Anyone who can create or modify a job configuration effectively gains command execution under the cron account. The 15-second timeout only limits runtime. It does not prevent background execution, file modification, credential access, or rapid data transmission. ### Attack Path 1. An attacker modifies `jobs/.json`. 2. The attacker adds a command diff containing arbitrary shell instructions in `diffs[].cmd`. 3. An operator or scheduled workflow runs preflight or `dac.py test-diff `. 4. `DiffEngine.run()` dispatches the speci ...[truncated 797 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/diff.py:71
Finding

Server-Side Request Forgery Through Unrestricted HTTP Diff URLs

Content
View full analysis
Optional[dict]: """Diff an HTTP endpoint by response hash.""" url = spec["url"] label = spec.get("label", url) jq_path = spec.get("jq") # dot-notation path for JSON extraction try: with urlopen(url, timeout=10) as resp: body = resp.read().decode("utf-8", errors="replace") # Optional JSON extraction if jq_path: try: data = json.loads(body) # Simple dot-notation traversal val = data for key in jq_path.strip(".").split("."): if isinstance(val, list): val = [item.get(key) if isinstance(item, dict) else item for item in val] elif isinstance(val, dict): val = val.get(key) else: break body = json.dumps(val) except Exception: pass h = _hash(body) prev = prev_hashes.get(url) if h != prev: return {"type": "http", "label": label, "change": f"response changed", "hash": h, "url": url, "snippet": body[:200]} return None except URLError as e: return {"type": "http", "label": label, "change": f"unreachable: {e}"} except Exception as e: return {"type": "http", "label": label, "change": f"error: {e}"} ``` ### Technical Analysis The HTTP diff engine fetches an arbitrary configuration-provided URL without validating: - The URL scheme. - The destination hostname or port. - The resolved IP address. - Whether the address is loopback, link-local, private, multicast, or otherwise reserved. - Redirect destinations. - DNS rebinding between validatio ...[truncated 1582 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/router.py:96
Finding

Potential Sensitive Job-Output Disclosure to Arbitrary Webhook Destinations

Content
View full analysis
Optional[str]: """POST JSON to a webhook URL.""" url = spec.target_id if not url: return "Webhook: no target_id (URL) specified" payload = json.dumps({ "job_id": self.job_id, "severity": severity, "summary": summary, "body": body[:2000], "timestamp": datetime.now(timezone.utc).isoformat(), }).encode() try: req = Request(url, data=payload, headers={"Content-Type": "application/json"}, method="POST") with urlopen(req, timeout=15) as resp: if resp.status >= 400: return f"Webhook returned {resp.status}" return None except Exception as e: return f"Webhook error: {e}" ``` ### Technical Analysis Webhook routing is declared functionality, so the transmission is not covert. However, the implementation posts job output to any URL supplied in configuration without a destination allowlist, data classification, redaction, confirmation step, or HTTPS requirement. The payload contains a summary and up to 2,000 characters of the complete job output. Scheduled job output commonly includes filesystem paths, system status, application errors, report contents, identifiers, and accidentally printed secrets. The vulnerability becomes especially serious when combined with unrestricted command diffs or jobs that process sensitive local information. ### Attack Path 1. An attacker or mistaken administrator configures a webhook route with an external destination. 2. A cron task produces sensitive output. 3. The workflow invokes `preflight.py --finish` or `dac.py finish` with that output. 4. `run_finish()` constructs the router from the job configuration. 5. The ...[truncated 743 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/router.py:76
Finding

Path Traversal and Unrestricted File Writes Through Job IDs and File Routes

Content
View full analysis
str: Path(LOG_DIR).mkdir(parents=True, exist_ok=True) log_path = str(Path(LOG_DIR) / f"{job_id}.log") now = datetime.now(timezone.utc).isoformat() entry = f"[{now}] [{severity.upper()}] {summary}\n" with open(log_path, "a") as f: f.write(entry) return log_path ``` ```python def _deliver_file(self, spec: RoutingSpec, severity: str, summary: str, body: str) -> Optional[str]: """Append to a file.""" file_path = spec.target_id if not file_path: return "File delivery: no target_id (path) specified" try: Path(file_path).parent.mkdir(parents=True, exist_ok=True) now = datetime.now(timezone.utc).isoformat() entry = f"\n## [{now}] [{severity.upper()}] {self.job_id}\n{summary}\n" if body and body != summary: entry += f"\n{body[:2000]}\n" with open(file_path, "a", encoding="utf-8") as f: f.write(entry) return None except Exception as e: return f"File delivery error: {e}" ``` ```python class CronState: def __init__(self, job_id: str): self.job_id = job_id self.path = Path(STATE_DIR) / f"{job_id}.json" self.data = self._load() ``` ```python def _save(self): Path(STATE_DIR).mkdir(parents=True, exist_ok=True) with open(self.path, "w") as f: json.dump(self.data, f, indent=2, default=str) ``` ```python def cmd_init(args): """Create a blank job config.""" Path(JOBS_DIR).mkdir(parents=True, exist_ok=True) job_path = Path(JOBS_DIR) / f"{args.job_id}.json" if job_path.exists(): print(f"Job config al ...[truncated 2535 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

This second mismatch finding similarly indicates the skill claims cron behavior, persistent memory, routing, token tracking, self-healing, and CLI integration that are not present in the referenced code. Such overclaiming can cause operators to deploy the skill under false assumptions, weakening operational security and potentially leading them to expose sensitive inputs to an implementation that lacks promised guardrails.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This second mismatch finding similarly indicates the skill claims cron behavior, persistent memory, routing, token tracking, self-healing, and CLI integration that are not present in the referenced code. Such overclaiming can cause operators to deploy the skill under false assumptions, weakening operational security and potentially leading them to expose sensitive inputs to an implementation that lacks promised guardrails.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

The documented use of subprocess.run with shell=True on values derived from job configurations or outputs creates a direct shell injection risk. An attacker who can influence job config, diff inputs, or routed output could execute arbitrary commands, read local data, alter files, pivot within the environment, or chain execution with network egress for exfiltration.

Content

Scanner excerpt · SKILL.md (reported line 20)May include surrounding context.

md
- Run in an **isolated environment** with limited network access (consider denying outbound webhooks if you do not want potential exfiltration).
- Ensure `openclaw` and `gog` CLIs exist and run with least privilege.
- Whitelist HTTP targets and restrict diff file paths to specific safe directories. Avoid diffs on secrets or system files.
- The code uses `subprocess.run(..., shell=True)` with values from job configs and outputs — unsanitized fields could allow shell injection.
- The skill can read arbitrary local files, make HTTP requests (including to internal IPs), and post outputs to external endpoints.
- Persisted state/logs may contain sensitive data from jobs — review storage permissions.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The documented ability to read local files, execute arbitrary commands, inject their output into DAC_CONTEXT, and route that data externally makes the skill substantially more dangerous than a normal cron wrapper. In a skill context, this broad I/O and prompt injection of harvested data creates a direct path to exfiltration of sensitive local information or command output.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

Supporting arbitrary file reads, command execution, and HTTP fetches as diff inputs, combined with external forwarding, is disproportionate to the stated cron purpose and creates a powerful collection-and-exfiltration primitive. This is especially dangerous because a malicious or careless job configuration could pivot the skill into reading secrets, querying internal services, or leaking outputs to third parties.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The review explicitly acknowledges arbitrary network access, including the possibility of contacting internal IPs and forwarding collected data to external endpoints. In the context of an agent skill with persistent memory and broad I/O, this materially increases the risk of SSRF, internal reconnaissance, and exfiltration beyond what users would expect from a cron utility.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The review describes shell-based delivery using shell=True together with values derived from job configuration and outputs, plus arbitrary command reads. That combination enables command injection if an attacker can influence config fields or delivered content, potentially resulting in arbitrary code execution and follow-on theft or destruction of data.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
98% confidence
Finding

The code passes a caller-controlled command string directly into subprocess.run with shell=True, which enables shell metacharacter interpretation and command injection if any untrusted input reaches command. In this skill context, the metadata explicitly notes broad I/O and shell execution capabilities, making this especially dangerous because an attacker or unsafe upstream configuration could execute arbitrary OS commands, exfiltrate data, or alter persistent state.

Content

Scanner excerpt · scripts/dial-a-cron.py (reported line 81)May include surrounding context.

python
start_time = time.time()
        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

This is a concrete tool-parameter abuse issue: the function treats spec["cmd"] as executable shell input and runs it unchanged. Because the skill explicitly supports persistent automation plus file, HTTP, and shell access, an attacker who can influence specs can turn the diff mechanism into a general-purpose command runner.

Content

Scanner excerpt · scripts/diff.py (reported line 114)May include surrounding context.

python
cmd = spec["cmd"]
    label = spec.get("label", cmd[:40])
    try:
        result = subprocess.run(
            cmd, shell=True, capture_output=True, text=True, timeout=15
        )
        output = result.stdout.strip()

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

This is a concrete tool-parameter abuse issue: the code forwards untrusted routing and message data into a shell-backed openclaw invocation. Because this skill explicitly has broad I/O and shell capabilities, successful injection would let an attacker pivot from message routing into arbitrary command execution and broader system compromise.

Content

Scanner excerpt · scripts/router.py (reported line 90)May include surrounding context.

python
target = spec.target_id or spec.to
        try:
            cmd = f'openclaw message send --to "{target}" --message "{message[:4000]}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return f"Telegram delivery failed: {result.stderr[:200]}"
            return None

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

The A2A sender exposes an execution sink where untrusted agent and message content are passed through the shell to openclaw. In a stateful automation skill that routes LLM-generated output, this is especially dangerous because attacker-controlled content may naturally flow into these fields from upstream jobs or external sources.

Content

Scanner excerpt · scripts/router.py (reported line 140)May include surrounding context.

python
agent = spec.target_id or spec.to
        try:
            cmd = f'openclaw a2a send --to "{agent}" --message "{message[:2000]}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return f"A2A delivery failed: {result.stderr[:200]}"
            return None

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

The email sender passes interpolated address/subject/body fields into a shell command, creating a direct command injection sink. Since cron output and summaries may include external content, an attacker could craft data that results in arbitrary local command execution when an email route is triggered.

Content

Scanner excerpt · scripts/router.py (reported line 156)May include surrounding context.

python
subject = f"[Dial-a-Cron] [{severity.upper()}] {self.job_id}"
            full_body = f"{summary}\n\n{body[:4000]}" if body != summary else summary
            cmd = f'gog gmail send --to "{to_addr}" --subject "{subject}" --body "{full_body[:4000]}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return f"Email failed: {result.stderr[:200]}"
            return None

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README promotes automatic routing of cron output to external channels such as Telegram and Slack, but it does not warn that job output may contain secrets, system details, or other sensitive data. In the context of a stateful cron/orchestration skill with broad file, HTTP, and shell capabilities, this omission increases the risk that users will unintentionally exfiltrate sensitive information through notifications or misconfigured routing.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill metadata advertises broad capabilities and the description explicitly warns about file, network, shell, and persistence behaviors, yet the manifest does not declare any tool scope restrictions such as permissions or allowed-tools. For a high-risk skill, omitting explicit scope boundaries increases the chance the platform or operator will grant overly broad access, making misuse or accidental data exposure more likely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

This markdown file documents config options for http, command, and file-based diffs, which can fetch remote data, execute commands, and access local files. It provides operational details but does not include any warning about privacy, system impact, or the risks of running commands and sending data over the network.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The channel reference includes telegram, a2a, webhook, and email, all of which can transmit job output to external systems or recipients. The description lists targets and examples but omits any warning that routed content may leave the local environment or contain sensitive information.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The review text claims the skill matches its stated purpose, but immediately documents materially broader undeclared behavior such as shelling out to external CLIs and relying on network/webhook delivery without declaring dependencies. This kind of incoherent framing can mislead operators into underestimating the true privilege and data-exposure surface of the skill.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
95% confidence
Finding

Unrestricted command execution in a cron-oriented skill is dangerous because it expands the skill from scheduling into a general-purpose execution environment. When combined with file access, prompt-context injection, persistence, and external routing, arbitrary command output can be harvested and sent off-system with little friction.

Content

Scanner excerpt · references/security-review.md (reported line 12)May include surrounding context.

md
The name/description match the code: state, diffs, routing, budgets, self-heal. However the code shells out to external CLIs (openclaw, gog) and expects network/webhook delivery while the skill metadata declares no required binaries or environment variables. Missing declared dependencies is an incoherence that can hide runtime failures or unexpected privilege use.

### Instruction Scope
SKILL.md instructs running preflight/finish and injecting DAC_CONTEXT into LLM prompts. The preflight and diff engine can read local files, execute arbitrary commands, and fetch arbitrary HTTP URLs; diff results and carry are printed into DAC_CONTEXT and can be routed externally. That means local files or command output can be collected and sent to third parties if routes/webhooks are configured — scope is broader than a simple cron wrapper.

### Install Mechanism
There is no install spec (instruction-only), which reduces installer risk. However repository includes runnable Python scripts that will be executed by the operator/agent; nothing is automatically downloaded from untrusted URLs or extracted archives. This is not high install risk, but consumers must be aware they're receiving executable scripts.

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

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/dac.py (reported line 100)May include surrounding context.

python
"downgradeModel": "grok-mini"
        }
    }
    with open(job_path, "w") as f:
        json.dump(config, f, indent=2)
    print(f"Created: {job_path}")
    print("Edit the file to add diffs, routes, and budget settings.")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code executes an arbitrary command via subprocess.run with shell=True, which is a safety-sensitive operation. Although there is a generic runtime log that the cron is running, there is no user-facing disclosure that shell execution will occur or what command is being run.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The comment says this is a 'Simple status check for all dial-a-crons', but the implementation does not inspect each JSON state's paused flag, failure count, or any runtime status. It always prints 'ACTIVE' for every discovered state file, which contradicts the stated intent of reporting status and can misrepresent paused or failed crons.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
98% confidence
Finding

The code executes a command taken directly from spec["cmd"] via subprocess.run(..., shell=True), which allows arbitrary shell syntax and command chaining. In this skill, specs are part of a broad-I/O, stateful automation system, so a user- or config-controlled command can lead to arbitrary code execution, data exfiltration, or destructive local actions.

Content

Scanner excerpt · scripts/diff.py (reported line 114)May include surrounding context.

python
cmd = spec["cmd"]
    label = spec.get("label", cmd[:40])
    try:
        result = subprocess.run(
            cmd, shell=True, capture_output=True, text=True, timeout=15
        )
        output = result.stdout.strip()

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

Medium
Category
Data Flow
Confidence
81% confidence
Finding

The log directory comes from the DAC_LOG_DIR environment variable and is used to form a write path without restriction. In environments where an attacker can influence process environment or startup configuration, this can redirect writes to unintended filesystem locations, potentially overwriting or planting files accessible to the running account.

Content

Scanner excerpt · scripts/router.py (reported line 81)May include surrounding context.

python
log_path = str(Path(LOG_DIR) / f"{job_id}.log")
        now = datetime.now(timezone.utc).isoformat()
        entry = f"[{now}] [{severity.upper()}] {summary}\n"
        with open(log_path, "a") as f:
            f.write(entry)
        return log_path

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

This constructs a shell command with attacker-controllable values (target and message) and executes it with shell=True. Even though the values are wrapped in double quotes, shell metacharacters such as command substitution can still be interpreted, enabling command injection and arbitrary command execution in the router context.

Content

Scanner excerpt · scripts/router.py (reported line 90)May include surrounding context.

python
target = spec.target_id or spec.to
        try:
            cmd = f'openclaw message send --to "{target}" --message "{message[:4000]}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return f"Telegram delivery failed: {result.stderr[:200]}"
            return None

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

The A2A delivery path builds a shell command from agent and message and executes it with shell=True. If routing specs or message content can be influenced by an attacker, this enables shell injection and execution of arbitrary local commands.

Content

Scanner excerpt · scripts/router.py (reported line 140)May include surrounding context.

python
agent = spec.target_id or spec.to
        try:
            cmd = f'openclaw a2a send --to "{agent}" --message "{message[:2000]}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return f"A2A delivery failed: {result.stderr[:200]}"
            return None

Static analysis

No suspicious patterns detected.