Back to skill

Security audit

Always On Agent Ops

Security checks across malware telemetry and agentic risk

Overview

This is a disclosed operations helper for keeping an OpenClaw agent running, with sensitive but user-directed cron, credential, polling, and cleanup behavior.

Install only on a dedicated, physically secured host. Use scoped queue tokens, keep secrets.env chmod 600 and out of git, verify every cron job ID and tool profile before enabling unattended runs, and confirm that the queue and notification destinations are systems you are authorized to use.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (12)

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill declares no explicit permissions while clearly requiring shell execution, file mutation, cron control, and network-capable helper scripts. This mismatch increases the chance of the skill being invoked or approved under an incomplete trust model, leading operators to underestimate its authority.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
The skill description frames the behavior as keeping an agent alive, but the body also performs log deletion, workspace measurement, authenticated outbound polling, and event-to-job dispatch orchestration. This broader behavior materially expands the attack and impact surface beyond what a user may reasonably consent to from the description alone.

External Transmission

Medium
Category
Data Exfiltration
Content
scripts/oauth-expiry-check.sh scripts/purge-retention.sh   # cron-message.sh is sourced: 644
# The scripts read nothing from the network until this file exists.
cat > scripts/secrets.env <<'EOF'
QUEUE_URL=https://api.acme-corp.example/queue
API_TOKEN=replace-me
EOF
chmod 600 scripts/secrets.env      # never commit it; it is not shipped with this skill
Confidence
78% confidence
Finding
https://api.acme-corp.example/

Credential Access

High
Category
Privilege Escalation
Content
| Credentials | A gateway token (loopback only), a provider auth profile, and any queue Bearer you add. All in `~/.openclaw/` or the OS keychain — never in git, never in the agent workspace. |
| Data persisted | Cron job definitions + run state (`~/.openclaw/cron/jobs.json`), the agent workspace and its memory files, logs. |
| Retention — **executed, not declared** | Logs: **30 days**, deleted by `scripts/purge-retention.sh` (`cr-22`, weekly). 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, the notification channel you name. Default is no network — with no `secrets.env`, the preflight emits `Human action required` and exits 0. |
| Network in | None. The gateway binds loopback. The optional loopback listener (below) is opt-in and never leaves the host. |
| Notifications | `${NOTIFY_CHANNEL}` receives short status lines. They leave the machine. Never put secrets, PII, or payload dumps in them. |
Confidence
80% confidence
Finding
secrets.env

Credential Access

High
Category
Privilege Escalation
Content
chmod +x scripts/preflight-queue.sh scripts/event-dispatch.sh \
         scripts/oauth-expiry-check.sh scripts/purge-retention.sh   # cron-message.sh is sourced: 644
# The scripts read nothing from the network until this file exists.
cat > scripts/secrets.env <<'EOF'
QUEUE_URL=https://api.acme-corp.example/queue
API_TOKEN=replace-me
EOF
Confidence
81% confidence
Finding
secrets.env

Credential Access

High
Category
Privilege Escalation
Content
| Variable | Where it lives | Who substitutes it |
|---|---|---|
| `QUEUE_URL`, `API_TOKEN` | `scripts/secrets.env` | the shell, at script run time |
| `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. |
| *(third-party, beyond `metadata.requires`)* | an authenticated queue endpoint you own or may call, a model provider account, a notification channel | you, before the first run |
Confidence
79% confidence
Finding
secrets.env

Credential Access

High
Category
Privilege Escalation
Content
# counts; an API renaming `items` to `results` degrades to a count, not a crash loop). "Counted zero"
# is NOT "found nothing to count": an empty array is 0, but no-count-and-no-array — a 200 carrying
# {"error":"unauthorized"}, 502 HTML, a captive portal — is an Error. A green "Nothing to do" while the
# queue fills is worse than a crash. No secrets.env, dead host, junk body -> one status line, exit 0,
# never a bare shell error. Hence no `set -e`: a broken preflight must still produce a line.
# Output: pf-10-queue - Nothing to do / Result: Queue empty. / ... / Details: count=0
# Output: pf-10-queue - Error / Result: Payload carries no count and no array... / Details: reason=no_countable_field
Confidence
75% confidence
Finding
secrets.env

Credential Access

High
Category
Privilege Escalation
Content
chmod +x scripts/preflight-queue.sh scripts/event-dispatch.sh \
         scripts/oauth-expiry-check.sh scripts/purge-retention.sh   # cron-message.sh is sourced: 644
# The scripts read nothing from the network until this file exists.
cat > scripts/secrets.env <<'EOF'
QUEUE_URL=https://api.acme-corp.example/queue
API_TOKEN=replace-me
EOF
Confidence
81% confidence
Finding
.env

Credential Access

High
Category
Privilege Escalation
Content
| Item | What this skill needs |
|---|---|
| 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 any queue Bearer you add. All in `~/.openclaw/` or the OS keychain — never in git, never in the agent workspace. |
| Data persisted | Cron job definitions + run state (`~/.openclaw/cron/jobs.json`), the agent workspace and its memory files, logs. |
| Retention — **executed, not declared** | Logs: **30 days**, deleted by `scripts/purge-retention.sh` (`cr-22`, weekly). 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, the notification channel you name. Default is no network — with no `secrets.env`, the preflight emits `Human action required` and exits 0. |
Confidence
74% confidence
Finding
keychain

Credential Access

High
Category
Privilege Escalation
Content
exit 0
fi
# shellcheck disable=SC1091
source "$DIR/secrets.env"

if [ -z "${QUEUE_URL:-}" ] || [ -z "${API_TOKEN:-}" ]; then
  cron_message "pf-10-queue" "Human action required" "QUEUE_URL or API_TOKEN missing from secrets.env." \
Confidence
78% confidence
Finding
secrets.env

Credential Access

High
Category
Privilege Escalation
Content
exit 0
fi
# shellcheck disable=SC1091
source "$DIR/secrets.env"

if [ -z "${QUEUE_URL:-}" ] || [ -z "${API_TOKEN:-}" ]; then
  cron_message "pf-10-queue" "Human action required" "QUEUE_URL or API_TOKEN missing from secrets.env." \
Confidence
78% confidence
Finding
.env"

Tool Parameter Abuse

High
Category
Tool Misuse
Content
| Gateway won't boot after an edit | Strict validation rejected an unknown key | `openclaw doctor --fix`, read the stripped-key list and the `.bak` |
| Config edited, behaviour unchanged | Last-known-good restored, or `gateway.*`/`plugins.*` needs a restart | `openclaw gateway restart`, then verify live state |
| Persistent "unauthorized" on loopback | A `launchctl setenv` token overrides the config file | Unset it, restart |
| Dispatch always says `Blocked`, no job ever runs | A killed run (restart, sleep, `kill -9`) orphaned its lock dir; the channel looks healthy | `rm -rf "${TMPDIR:-/tmp}"/openclaw-event-*.lock`. The TTL reclaims it after `LOCK_TTL_MIN`; lower it if your jobs are short |
| `cr-23` says `ok` while every turn fails | Several profiles in one auth file, and a fresh one masked the dead one — the shipped checker takes the **earliest** expiry precisely for this | Read `field` in the JSON: it names the profile that decided. If you wrote your own checker, take the min — and make "cannot determine" a `warn`, never an `ok` |
| `cr-22` reports `Not an OpenClaw home` | `OPENCLAW_HOME` points somewhere without `openclaw.json` or `agents/` — a typo or a stale env var | Intended: the purge refuses any dir it cannot prove is an agent home rather than deleting on faith. Fix the variable |
| All runs fail overnight, no alert | OAuth expired headless | `cr-23` + human relogin; consider an API key |
Confidence
77% confidence
Finding
rm -rf "${TMPDIR:-/tmp}"/

VirusTotal

65/65 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

No suspicious patterns detected.