Back to skill

Security audit

Always-On Agent Ops — cron dispatch, OAuth expiry, queue preflight

Security checks for vulnerabilities and agentic risk

Overview

This skill is a transparent OpenClaw operations helper whose polling, dispatch, auth-expiry checks, and log cleanup are disclosed and scoped to the stated purpose.

Install this only for an OpenClaw host you control. Keep the queue token read-only and stored outside the workspace with 600/400 permissions, review each cron before enabling it, keep runtime exec permissions as narrow as possible, verify purge dry-runs before setting PURGE_DRY_RUN=0, and do not route secrets or private payloads to notification channels.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (33)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a full operational automation system with multiple behaviors and resource interactions. The actual code chunk is narrowly scoped to formatting and printing status output. This is not merely an omitted supporting detail; the code’s primary purpose is materially different from the declared purpose, and none of the key declared capabilities are implemented in the supplied chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code chunk is narrowly focused on checking whether an OpenClaw auth profile is missing, corrupt, expired, near expiry, or stale based on mtime. While this partially aligns with the declared mention of reading auth-profiles.json expiry, it does not implement the majority of the described behavior. There is no cron creation, no log deletion, no HTTPS communication, no queue polling, no event dispatch, and no lock management. Therefore the declared description materially overstates and misrepresents what this code actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description describes a multi-function operations skill with networking, scheduling, dispatch/locking, and auth-profile expiry handling. This code chunk does not implement those capabilities. It only performs guarded local log cleanup and reporting for .log files under an OpenClaw home. While 'deletes old logs' is consistent with part of the description, the actual code shown is materially narrower than the declared purpose and lacks the key advertised behaviors.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardc

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardc

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardc

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardc

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
ue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardc

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The skill is designed to access and use credentials, including a queue Bearer token and auth profile data. Even though it prescribes safer storage, any skill that reads local secrets and performs authenticated network requests materially increases risk because compromise of the agent or mis-scoped jobs could exfiltrate or misuse those credentials.

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardcoded list, with `mkdir` locks. `oauth-expiry-check.sh`: **reads `auth-profiles.json`** and prints only key paths and expiry epochs — never a token value. `purge-retention.sh`: **deletes `*.log` files** under `$OPENCLAW_HOME/logs` past 30 days — dry run until you arm it. `cron-message.sh`: prints a status line. |
| What you create with it | Persistent OpenClaw **cron jobs** (§Cron layer), including a daily `gateway restart`. Every job is yours to review before `enabled: true`. |
| Host access | A logged-in GUI desktop session, permanently. The daemon is not headless. |
| Credentials | A gateway token (loopback only), a provider auth profile, and a queue Bearer. The Bearer lives in `~/.config/always-on-agent-ops/secrets.env` (chmod 600) — **outside** the skill directory, never in git, never in the agent workspace. Scope it read-only to the queue count, nothing else. |
| Data persisted | Cron job definitions, run state and run history (the shared SQLite state DB, `~/.openclaw/state/openclaw.sqlite`), the agent workspace and its memory files, logs. |
| Retention — **executed once armed** | Logs: **30 days**, deleted by `scripts/purge-retention.sh` (`cr-22`, weekly) — it ships in dry-run mode and deletes nothing until you set `PURGE_DRY_RUN=0` after reading one dry-run list. Orphaned lock dirs: **15 min** TTL, reclaimed by the dispatcher. Cron run state: life of the job — it goes when you delete the job. Workspace/memory: **no automatic purge** — `cr-22` reports its size so growth cannot stay invisible; the number you keep is yours to set. |
| Network out | Only what YOU configure: the queue you poll (HTTPS only; plain `http://` is refused except to `localhost`/`127.0.0.1`/`[::1]`), the notification channel you name. Default is no network — with no config file, the pre
...[truncated 24 chars]

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The setup instructions create a plaintext secrets.env file containing an API token. Although protected by umask and location outside the repo, plaintext credential files remain a sensitive attack surface on an always-on host, especially when paired with shell access and automated jobs.

Content

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

md
# Secrets live OUTSIDE the skill directory: a skill folder gets copied, synced, zipped and
# re-published; a config dir does not. The preflight reads nothing from the network until this exists.
install -d -m 700 ~/.config/always-on-agent-ops
( umask 077; cat > ~/.config/always-on-agent-ops/secrets.env <<'EOF'
QUEUE_URL=https://queue.example.com/v1/pending
API_TOKEN=replace-me
EOF

Credential Access

High
Category
Privilege Escalation
Confidence
89% confidence
Finding

The skill's preflight logic explicitly reads a local secrets file to obtain a Bearer token for authenticated requests. This is expected functionality, but in a security review it is still a true sensitive-capability finding because any compromise of the script chain or execution context can turn that access into credential theft or unauthorized API use.

Content

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

API_TOKEN=replace-me EOF ) ls -l ~/.config/always-on-agent-ops/secrets.env # expect -rw------- and your user

text

The preflight **refuses** (one `Human action required` line, exit 0, no network) when the file is absent, empty, a symlink, not owned by you, has any group/other bit (`chmod 600` or `400` only), sits inside the skill directory, or when `QUEUE_URL` is not `https://` (loopback `http://` excepted) or carries `user@`. The file is **parsed for two keys, never sourced** — it cannot execute code. The Bearer goes to curl on stdin, never in argv, so it does not show in `ps`. Other path? `AOA_SECRETS_FILE=/abs/path` (absolute only). Prefer the OS keychain? Have a wrapper write the file at boot with `umask 077`; the checks are the same.

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The documentation centralizes multiple sensitive variables and notes that some are entered manually into configuration. In unattended systems, this increases the chance of accidental secret persistence in config files, backups, or copied skill artifacts, especially since the document also warns that literal placeholders may remain unchanged.

Content

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

md
| Variable | Where it lives | Who substitutes it |
|---|---|---|
| `QUEUE_URL`, `API_TOKEN` | `~/.config/always-on-agent-ops/secrets.env` (or `$AOA_SECRETS_FILE`) | parsed by the preflight at run time — never sourced |
| `PURGE_DRY_RUN`, `LOG_RETENTION_DAYS`, `LOCK_TTL_MIN` | environment (optional) | the shell; default dry run, `30`, `15` |
| `OPENCLAW_HOME`, `OPENCLAW_AGENT_ID` | environment (optional) | the shell; default `~/.openclaw` and `main` |
| `GATEWAY_TOKEN`, `MODEL_PRIMARY`, `MODEL_FALLBACK`, `RUNTIME_ID`, `PROVIDER`, `*_PLUGIN`, `CHANNEL`, `NOTIFY_CHANNEL` | `openclaw.json` / job definitions below | **you, by hand** — OpenClaw does not interpolate `${...}` in its config. A literal `${GATEWAY_TOKEN}` left in the file is a literal token, and strict validation will not save you from it. |

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
93% confidence
Finding

The skill discusses and relies on access to runtime configuration in ~/.codex/config.toml and notes that a local codex runtime may default to no approvals and full host access. That creates a dangerous trust boundary: an unattended agent could inherit permissive runtime settings outside the declared OpenClaw tool restrictions and execute commands with broad filesystem access.

Content

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

Output: no results <- legacy -/* namespace is dead; doctor --fix rewrites it

text

**The runtime carries its own execution posture.** A local `codex` runtime defaults to *no approvals + full host access*, configured below OpenClaw (`~/.codex/config.toml`: `approval_policy`, `sandbox_mode`, trusted dirs). A tool profile narrows what OpenClaw offers, not what that runtime may do. State the shell policy where it is enforced — `tools.exec.mode: "deny"` for an agent that must not run commands — and read the runtime's own config on the host. Proof, not assumption: ask the agent to run `id -un` in a test turn; it must be refused.

**For an unattended host, use an API key.** It is the supported path: pay-per-token, no expiry, no human in the loop. OAuth is subscription-priced but expires in days and needs a browser — on a 24/7 host you trade money against a recurring silent outage, and the outage wins. `auth-profiles.json` (in `~/.openclaw/agents/<agent>/agent/`) is a **credential, not a config file** — treat it like a private key: never in git, no SecretRef support. Copying it to another host is *gray-area and not the recommended path*: only acceptable if your provider's terms allow that account on that machine. Check the terms before you copy.

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

The oauth-expiry-check workflow reads credential-bearing auth-profiles.json metadata. Even if it claims not to print token values, handling credential stores from shell tooling is sensitive because parser bugs, future script changes, or verbose error paths can accidentally expose secrets.

Content

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

bash
OPENCLAW_HOME=~/.openclaw ./scripts/oauth-expiry-check.sh
# EARLIEST epoch expiry wins: a fresh profile must never mask a dead one, nor a 90-day refresh token
# a dead access token. It names the deciding `field` and NEVER exits non-zero. "Cannot determine" is
# NEVER `ok`: corrupt JSON, no python3, an unreadable (ISO-8601) expiry all emit `warn` — the mtime
# proxy is reached ONLY when the file parses and genuinely carries no expiry field at all.
# Output: {"status":"warn","source":"expires_at","field":"profiles.dead.expires_at","remaining_days":-1,"note":"...relogin..."}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/oauth-expiry-check.sh (reported line 87)May include surrounding context.

sh
clean = lambda p: re.sub(r"[^A-Za-z0-9_.\[\]-]", "_", p)[:200]
if good:
    # min, not max: a fresh profile must never mask an expired one, and a
    # long-lived refresh token must never mask a dead access token.
    v, p = min(good, key=lambda t: t[0])
    print("EPOCH|%d|%s" % (int(v), clean(p)))
elif bad:

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/preflight-queue.sh (reported line 10)May include surrounding context.

sh
#
# Network + credential behaviour, declared:
#   - Reads QUEUE_URL and API_TOKEN from a config file OUTSIDE the skill directory:
#     ${AOA_SECRETS_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/always-on-agent-ops/secrets.env}
#   - The file is PARSED (two keys), never sourced: it cannot execute code.
#   - Refused unless it is a regular file owned by you with no group/other bits (600/400).
#   - QUEUE_URL must be https://. Plain http:// is accepted ONLY for a loopback host

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/preflight-queue.sh (reported line 36)May include surrounding context.

sh
#
# Network + credential behaviour, declared:
#   - Reads QUEUE_URL and API_TOKEN from a config file OUTSIDE the skill directory:
#     ${AOA_SECRETS_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/always-on-agent-ops/secrets.env}
#   - The file is PARSED (two keys), never sourced: it cannot execute code.
#   - Refused unless it is a regular file owned by you with no group/other bits (600/400).
#   - QUEUE_URL must be https://. Plain http:// is accepted ONLY for a loopback host

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/preflight-queue.sh (reported line 38)May include surrounding context.

sh
#
# Network + credential behaviour, declared:
#   - Reads QUEUE_URL and API_TOKEN from a config file OUTSIDE the skill directory:
#     ${AOA_SECRETS_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/always-on-agent-ops/secrets.env}
#   - The file is PARSED (two keys), never sourced: it cannot execute code.
#   - Refused unless it is a regular file owned by you with no group/other bits (600/400).
#   - QUEUE_URL must be https://. Plain http:// is accepted ONLY for a loopback host

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/preflight-queue.sh (reported line 56)May include surrounding context.

sh
#
# Network + credential behaviour, declared:
#   - Reads QUEUE_URL and API_TOKEN from a config file OUTSIDE the skill directory:
#     ${AOA_SECRETS_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/always-on-agent-ops/secrets.env}
#   - The file is PARSED (two keys), never sourced: it cannot execute code.
#   - Refused unless it is a regular file owned by you with no group/other bits (600/400).
#   - QUEUE_URL must be https://. Plain http:// is accepted ONLY for a loopback host

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill explicitly describes shell-scripted operations, cron management, file deletion, and network polling, but it does not declare an explicit tool/permission scope. In an agent ecosystem, missing scope boundaries can cause the platform to grant broader shell access than reviewers expect, increasing the blast radius of misuse or prompt-injection-driven actions.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardcoded list, with `mkdir` locks. `oauth-expiry-check.sh`: **reads `auth-profiles.json`** and prints only key paths and expiry epochs — never a token value. `purge-retention.sh`: **deletes `*.log` files** under `$OPENCLAW_HOME/logs` past 30 days — dry run until you arm it. `cron-message.sh`: prints a status line. |
| What you create with it | Persistent OpenClaw **cron jobs** (§Cron layer), including a daily `gateway restart`. Every job is yours to review before `enabled: true`. |
| Host access | A logged-in GUI desktop session, permanently. The daemon is not headless. |
| Credentials | A gateway token (loopback only), a provider auth profile, and a queue Bearer. The Bearer lives in `~/.config/always-on-agent-ops/secrets.env` (chmod 600) — **outside** the skill directory, never in git, never in the agent workspace. Scope it read-only to the queue count, nothing else. |
| Data persisted | Cron job definitions, run state and run history (the shared SQLite state DB, `~/.openclaw/state/openclaw.sqlite`), the agent workspace and its memory files, logs. |
| Retention — **executed once armed** | Logs: **30 days**, deleted by `scripts/purge-retention.sh` (`cr-22`, weekly) — it ships in dry-run mode and deletes nothing until you set `PURGE_DRY_RUN=0` after reading one dry-run list. Orphaned lock dirs: **15 min** TTL, reclaimed by the dispatcher. Cron run state: life of the job — it goes when you delete the job. Workspace/memory: **no automatic purge** — `cr-22` reports its size so growth cannot stay invisible; the number you keep is yours to set. |
| Network out | Only what YOU configure: the queue you poll (HTTPS only; plain `http://` is refused except to `localhost`/`127.0.0.1`/`[::1]`), the notification channel you name. Default is no network — with no config file, the pre
...[truncated 24 chars]

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
| What the shipped scripts do | `preflight-queue.sh`: one **authenticated HTTPS GET** (Bearer) to a queue URL you configure. `event-dispatch.sh`: runs `openclaw cron get/run` on job ids from a closed, hardcoded list, with `mkdir` locks. `oauth-expiry-check.sh`: **reads `auth-profiles.json`** and prints only key paths and expiry epochs — never a token value. `purge-retention.sh`: **deletes `*.log` files** under `$OPENCLAW_HOME/logs` past 30 days — dry run until you arm it. `cron-message.sh`: prints a status line. |
| What you create with it | Persistent OpenClaw **cron jobs** (§Cron layer), including a daily `gateway restart`. Every job is yours to review before `enabled: true`. |
| Host access | A logged-in GUI desktop session, permanently. The daemon is not headless. |
| Credentials | A gateway token (loopback only), a provider auth profile, and a queue Bearer. The Bearer lives in `~/.config/always-on-agent-ops/secrets.env` (chmod 600) — **outside** the skill directory, never in git, never in the agent workspace. Scope it read-only to the queue count, nothing else. |
| Data persisted | Cron job definitions, run state and run history (the shared SQLite state DB, `~/.openclaw/state/openclaw.sqlite`), the agent workspace and its memory files, logs. |
| Retention — **executed once armed** | Logs: **30 days**, deleted by `scripts/purge-retention.sh` (`cr-22`, weekly) — it ships in dry-run mode and deletes nothing until you set `PURGE_DRY_RUN=0` after reading one dry-run list. Orphaned lock dirs: **15 min** TTL, reclaimed by the dispatcher. Cron run state: life of the job — it goes when you delete the job. Workspace/memory: **no automatic purge** — `cr-22` reports its size so growth cannot stay invisible; the number you keep is yours to set. |
| Network out | Only what YOU configure: the queue you poll (HTTPS only; plain `http://` is refused except to `localhost`/`127.0.0.1`/`[::1]`), the notification channel you name. Default is no network — with no config file, the pre
...[truncated 24 chars]