OpenClaw Cron Privilege Escalation
CVE-2026-62202 is a high-severity privilege escalation in OpenClaw's isolated cron jobs, allowing lower-trust callers to bypass denied execution controls.
Overview
CVE-2026-62202 is a privilege escalation vulnerability affecting OpenClaw versions 2026.6.1 through 2026.6.8. The flaw resides in the platform’s isolated cron job subsystem, which is designed to enforce execution boundaries between callers of differing trust levels. Under normal operation, lower-trust callers — such as tenant-scoped service accounts or restricted API consumers — are explicitly denied access to certain execution tools. The vulnerability breaks that boundary by failing to properly sanitize and validate input paths supplied to the cron scheduler, allowing a constrained caller to re-enter the execution context of tools that were explicitly denied to them.
The vulnerability was identified by security researchers analyzing OpenClaw’s job isolation primitives and reported through the project’s coordinated disclosure process. It carries a CVSS score of 8.8 (High), reflecting the combination of low-complexity exploitation, no requirement for user interaction, and the significant impact on confidentiality, integrity, and availability once privilege escalation is achieved. Organizations running multi-tenant OpenClaw deployments or those exposing cron scheduling interfaces to less-privileged internal consumers are at greatest risk.
OpenClaw is used in enterprise automation and orchestration contexts where role-based execution constraints are an explicit security control. Any bypass of those controls represents a meaningful degradation of the security posture of the deployment, not merely a theoretical edge case.
Technical Analysis
The root cause is a failure of input path validation in the cron job dispatcher. OpenClaw’s isolated cron feature constructs execution contexts by resolving tool paths at scheduling time. When a job is registered, the scheduler accepts a caller-supplied tool_path parameter and uses it to locate the executable or handler. The authorization check — which determines whether the calling principal is permitted to invoke a given tool — is performed against a normalized form of the path. However, the normalization step occurs after the path is stored and before it is checked, leaving a window in which a crafted path can resolve to an authorized tool identifier while the raw input actually points to a denied one.
The vulnerable pattern looks like this:
# openclaw/cron/dispatcher.py (vulnerable, ~2026.6.1 - 2026.6.8)
def schedule_job(caller_context, tool_path, cron_expr):
# Authorization check uses normalized path
normalized = os.path.normpath(tool_path)
if not acl_engine.is_permitted(caller_context, normalized):
raise PermissionDenied(f"Caller lacks access to tool: {normalized}")
# Job is stored and later executed using the RAW, un-normalized path
job = CronJob(
caller=caller_context.principal_id,
tool_path=tool_path, # <-- raw attacker-controlled value persisted
schedule=cron_expr,
)
job_store.save(job)
return job.id
def execute_job(job_id):
job = job_store.get(job_id)
# Execution resolves the raw path — no re-authorization at execution time
tool = tool_registry.resolve(job.tool_path)
tool.run(caller_context=job.caller)
The critical flaw is the asymmetry between what is checked (the normalized path) and what is executed (the raw path). A lower-trust caller can supply a path like ../../privileged_tool or a symlink-equivalent alias that normalizes to an allowed tool during the ACL check, but resolves at execution time to a denied, higher-privilege tool. Because execute_job performs no re-authorization and trusts the persisted tool_path verbatim, the attacker’s chosen tool runs under the original caller’s persisted identity — effectively inheriting whatever execution privileges the cron runtime carries, which in many deployments is elevated.
Additionally, because the job is persisted to job_store, the attacker achieves persistence: the malicious cron entry will continue executing on schedule until explicitly removed, even if the caller’s session or token is subsequently revoked.
Impact
An attacker who can register a cron job — a capability granted to lower-trust principals in typical OpenClaw deployments — can escalate to execute arbitrary tools that were explicitly denied to them by the platform’s ACL engine. In practice this means:
- Execution of privileged automation tools: Actions restricted to administrative roles (e.g., data export, configuration mutation, secret retrieval) can be invoked by tenant-scoped or read-only service accounts.
- Persistence beyond session lifetime: Because the exploit is embedded in a scheduled job rather than a live session, revocation of the attacker’s credentials does not automatically terminate the malicious execution chain.
- Lateral movement in multi-tenant environments: If the cron runtime shares an execution context across tenants, a compromised low-trust account in one tenant could invoke tools belonging to another tenant’s scope.
- Data exfiltration and integrity compromise: Tools gated behind elevated ACLs frequently operate on sensitive data stores. Unauthorized invocation directly threatens data confidentiality and integrity.
The CVSS 8.8 score reflects: Attack Vector: Network, Attack Complexity: Low, Privileges Required: Low, User Interaction: None, Scope: Unchanged, Confidentiality/Integrity/Availability Impact: High. The Low complexity and Low privilege requirement make this straightforwardly exploitable by any authenticated user with cron scheduling access.
How to Fix It
The fix requires two coordinated changes: re-authorization at execution time, and strict path validation before persistence.
Upgrade immediately to OpenClaw 2026.6.9 or later:
# pip
pip install "openclaw>=2026.6.9"
# poetry
poetry add "openclaw>=2026.6.9"
# pipx-managed installations
pipx upgrade openclaw
The corrected dispatcher pattern validates and normalizes the path before persistence, and re-authorizes at execution time against the normalized, registry-resolved tool identifier:
# openclaw/cron/dispatcher.py (patched, 2026.6.9+)
def schedule_job(caller_context, tool_path, cron_expr):
# Normalize and resolve to a canonical registry key BEFORE storing
normalized = os.path.normpath(tool_path)
resolved_tool_id = tool_registry.resolve_to_id(normalized)
if resolved_tool_id is None:
raise ValueError(f"Unknown tool path: {tool_path}")
if not acl_engine.is_permitted(caller_context, resolved_tool_id):
raise PermissionDenied(f"Caller lacks access to tool: {resolved_tool_id}")
# Persist only the canonical, registry-verified tool ID — never raw input
job = CronJob(
caller=caller_context.principal_id,
tool_id=resolved_tool_id, # canonical ID, not raw path
schedule=cron_expr,
)
job_store.save(job)
return job.id
def execute_job(job_id):
job = job_store.get(job_id)
# Re-authorize at execution time using the stored canonical ID
caller_context = principal_store.get(job.caller)
if not acl_engine.is_permitted(caller_context, job.tool_id):
audit_log.warn(f"Blocked stale job {job_id}: caller no longer permitted")
return
tool = tool_registry.get_by_id(job.tool_id)
tool.run(caller_context=caller_context)
Beyond patching: audit your existing job store for any jobs registered by low-trust principals that reference tool IDs outside their expected scope. Treat such entries as potentially malicious and remove them before upgrading.
Our Take
This vulnerability exemplifies a class of authorization flaw that persists in production systems because the check and the action are separated in time and code path. Developers correctly implement the authorization gate, then inadvertently introduce a gap by storing and later consuming untrusted input that was never fully canonicalized. The authorization logic is technically present — it just operates on a different value than the one that ultimately drives behavior. SAST tooling that only checks for the presence of an authorization call will miss this entirely.
The persistence angle is particularly important for enterprises. Most incident response playbooks focus on credential revocation as a containment step. A job-store-based exploit survives credential rotation, making detection and eradication harder. Enterprises should treat their scheduled job inventories as a security-sensitive artifact and include them in regular access reviews.
Detection with SAST
This vulnerability class maps to CWE-863 (Incorrect Authorization) and CWE-706 (Use of Incorrectly-Resolved Name or Reference). SAST detection should target the following patterns:
- Check-then-use on unsanitized input: Flag any code path where a user-supplied path or identifier is passed to an authorization function, but the original variable (not the normalized result) is subsequently stored or passed to an execution sink.
- Authorization absence at execution sinks: Flag execution sinks (subprocess calls, plugin/tool invocations, handler dispatches) that consume data originating from a persistence store without a local re-authorization call in scope.
- Asymmetric normalization: Flag cases where
os.path.normpath,os.path.realpath, or equivalent canonicalization functions are applied to a variable, but the un-normalized original is used downstream.
Offensive360’s SAST engine models taint flow through persistence boundaries — tracking whether a value stored to a database or job queue was authorized pre-storage and whether authorization is re-evaluated post-retrieval. Naive taint analysis terminates at the storage boundary; robust analysis must follow the full data lifecycle from ingestion through persistence to execution.
References
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-62202-class vulnerabilities and thousands of other patterns — across 60+ languages.