T09 · Insecure Skill Coding Practices
- Location
ops-monitor.py:556- Finding
Self-Declared Risk Labels Do Not Enforce Read-Only Execution
- Content
View full analysis
Vulnerability Details
File Location:
ops-monitor.py:304-330,ops-monitor.py:556-600
Vulnerability Type: Arbitrary command execution through unenforced trust labels
Risk Level: HighVulnerable Code
python risk = raw.get("risk") if not isinstance(risk, str) or risk not in ALLOWED_RISKS: raise ValueError(f"Invalid job {jid}: risk must be one of {sorted(ALLOWED_RISKS)}") cwd_raw = raw.get("cwd") if cwd_raw is None: cwd = OPENCLAW_HOME elif isinstance(cwd_raw, str) and cwd_raw.strip(): cwd = Path(cwd_raw).expanduser() else: raise ValueError(f"Invalid job {jid}: cwd must be a non-empty string path") commands_raw = raw.get("commands") if not isinstance(commands_raw, dict): raise ValueError(f"Invalid job {jid}: commands must be an object") commands: dict[str, list[str]] = {} for k, v in commands_raw.items(): if not isinstance(k, str): continue argv = _as_argv(v) if argv is None: raise ValueError(f"Invalid job {jid}: commands.{k} must be a non-empty argv list") commands[k] = argvpython def maybe_autorun_start( *, job: JobConfig, status: JobStatus, now: float, state_job: dict[str, Any], defaults: JobDefaults, dry_run: bool, ) -> str | None: if job.kind != "long_running_read": return None if not job.enabled: return None if job.risk != "read_only": return "AUTORUN: blocked (risk != read_only)" if status.running or status.completed: return None if not _policy_bool(job, defaults, "autoResume"): return None last = state_job.get("lastAutoResumeAt") try: last_ts = float(last) if isinstance(last, (int, float)) else 0.0 except Exception: last_ts = 0.0 backoff = float(_policy_int(job, defaults, "autoResumeBackoffSeconds") or 0) if backoff and now - last_ts < ba ...[truncated 3138 chars]- Remediation
View remediation
Remediation Suggestions
- Explicitly document that the job configuration is executable trusted code, not a security policy boundary.
- Restrict ownership and permissions on the configuration, state directory, and invoked scripts so only the dedicated monitor administrator can modify them.
- Run the monitor under a dedicated, unprivileged operating-system account with access only to required paths.
- Replace arbitrary command arrays for automatic execution with an allowlist of audited executable paths and fixed argument schemas.
- Resolve executable paths to canonical absolute paths and reject writable, relative, unexpected, or symlink-substituted executables.
- Store approved command manifests or hashes and verify their integrity before automatic execution.
- Apply operating-system sandboxing to restrict filesystem writes, process access, and network destinations.
- Disable networking for genuinely local read jobs unless a reviewed job explicitly requires it.
- Keep
autoResumedisabled by default and require a separate, protected approval record rather than an approval field in the same mutable configuration. - Add negative tests proving that mislabeled write commands cannot modify files or access the network.
