T09 · Insecure Skill Coding Practices
- Location
plug.sh:44- Finding
Command Injection Through Unsafe Python Heredoc and Cron Construction
- Content
View full analysis
"$PULSE_HOME/registry/${skill_safe}.conf" <- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
Pulse Board is a disclosed cron digest tool, but it has review-worthy risks around persistent cron changes, broad secrets-file execution, unsafe cron construction, and sending log content to configured agents or messaging services.
Review this skill carefully before installing. It is not evidenced as malicious, but it can modify your user crontab, persist scheduled commands, source a shared secrets file as shell code, store notification credentials in plaintext config, and send operational log content to an OpenClaw agent or messaging service. Use it only with trusted cron jobs, a local or low-privilege summarization agent, tightly permissioned config/secrets files, and logs that do not contain secrets or sensitive incident data.
plug.sh:44Command Injection Through Unsafe Python Heredoc and Cron Construction
digest-agent.sh:16Unconditional Execution of a Shared Secrets File
digest-agent.sh:88Stored Prompt Injection Through Untrusted Digest Log Messages
install.sh:218Plaintext Delivery Credentials Created Without Restrictive Permissions
unplug.sh:50Overbroad Crontab Removal Through Unanchored Pattern Matching
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
mmary, not the raw log
- Raw log accessible on demand via `last-digest.md` or by asking your agent
---
## [1.0.5] - 2026-03-09
### Fixed / Security
- `install.sh` secrets env patch is now **explicit opt-in**: the installer
shows exactly which keys are missing and why, then asks for confirmation
before appending anything. Nothing is written to the secrets env file
silently.
- `install.sh` crontab change is now **announced before it happens**: the
installer prints the exact entries it will add and asks for confirmation.
- `plug.sh` `wrap_cmd` now carries an explicit comment explaining that the
secrets env is sourced in the cron shell context only — it is never read,
parsed, logged, or transmitted by `plug.sh` itself.
- `_meta.json` now fully declares `requires.binaries`, `requires.env_vars`,
`filesystem.creates/reads/modifies`, `network.external_endpoints`, and
`credentials` — eliminating the metadata/behavior mismatch flagged by the
OpenClaw security scanner.
-
The declared description is about a digest/summary pipeline: collecting scheduled skill outcomes, composing human-readable summaries with an agent, delivering them to messaging platforms, and installation/plugging workflows. The supplied code does none of that. Instead, it is an unplug.sh utility focused on removing a skill's registry entry and cron schedule. This is a materially different primary purpose and involves a capability not represented in the declared description: uninstalling/disconnecting scheduled skills by modifying config files and the user's crontab. Therefore the description does not accurately represent this code chunk.
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
`deliver.sh` |
| `~/.pulse-board/registry/<skill>.conf` | Written by `plug.sh`, removed by `unplug.sh` |
### Crontab
| Script | Action |
|--------|--------|
| `install.sh` | Adds two digest cron entries (`pulse-board-morning`, `pulse-board-evening`) |
| `plug.sh` | Adds one wrapped cron entry per skill (`# pulse-board:<skill>`) |
| `unplug.sh` | Removes the matching cron entry for a skill |
All crontab writes are done via `python3 subprocess`. Existing entries are never modified.
### Secrets env file
`install.sh` will ask for explicit confirmation before appending anything.
It may add `LLM_API_KEY=ollama` and `OPENCLAW_WORKSPACE=<path>` if missing.
### Network
- **Telegram:** `POST https://api.telegram.org/bot<token>/sendMessage`
- **Discord:** `POST <your webhook URL>`
- **OpenClaw agent:** `openclaw agent --agent <id> --message <prompt> --json` (local gateway call)
⚠️ The raw log is included in the prompt. If your agent uses a remote/cloud LLM, log content will be transmitt
Accessing and sourcing a shared secrets file grants this script visibility into credentials beyond those strictly required for delivery. Because source executes shell content, a compromised or overly broad secrets file can lead to credential exposure or arbitrary code execution in the context of this script.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
The sourcing operation completes on this line, meaning all variables and any embedded shell statements from the shared secrets file are imported into the runtime. In a skill ecosystem, this increases blast radius because a simple delivery helper now depends on and can be influenced by a central secret-bearing shell file.
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
g() { printf "\033[0;32m%s\033[0m\n" "$*" >&2; }
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
CONFIG_FILE="$PULSE_HOME/config/pulse.yaml"
[[ -f "$HOME/.openclaw/shared/secrets/openclaw-secrets.env" ]] && \
{ set +u; source "$HOME/.openclaw/shared/secrets/openclaw-secrets.env"; set -u; }
# ── Helpers ───────────────────────────────────────────────────────────────────
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
if [[ -f "$LOCK" ]]; then
AGE=$(( $(date +%s) - $(date -r "$LOCK" +%s 2>/dev/null || echo 0) ))
[[ $AGE -lt 3600 ]] && { y "digest-agent: already running (${AGE}s). Exiting."; exit 0; }
y "digest-agent: removing stale lock (${AGE}s)."; rm -f "$LOCK"
fi
touch "$LOCK"; trap 'rm -f "$LOCK"' EXIT
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
#!/usr/bin/env bash
# Pulse Board — install.sh
# Interactive installer. Run once. Does everything.
# No sudo. No root. No system path writes outside ~/.pulse-board/
#
# What this script does — in full:
# 1. Creates ~/.pulse-board/{config,logs,registry,locks}
# 2. Writes ~/.pulse-board/config/pulse.yaml (once — never overwrites)
# 3. Adds two digest cron entries to your user crontab
# 4. Optionally appends LLM_API_KEY and OPENCLAW_WORKSPACE to your
# secrets env file — only if you confirm at the prompt.
#
# Nothing is written silently. Every change is announced before it happens.
set -uo pipefail
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PULSE_HOME="${PULSE_HOME:-$HOME/.pulse-board}"
# ── UI helpers ────────────────────────────────────────────────────────────────
green() { printf "\033[0;32m%s\033[0m\n" "$
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
#!/usr/bin/env bash
# Pulse Board — plug.sh
# Discovers cron jobs from system crontab and OpenClaw registry,
# presents a menu, and wires selected jobs into Pulse Board.
# No sudo. No root.
#
# Crontab writes: uses python3 subprocess to avoid shell escaping issues.
# Secrets env: if a secrets_env path is set in pulse.yaml, plug.sh wraps
# each wired cron command with "source <secrets_env> && <cmd>" so that the
# skill's runtime environment matches what you configured in install.sh.
# The secrets file is never read, parsed, or transmitted — only sourced
# in the cron shell context, exactly as if you had run it manually.
set -uo pipefail
SKILL_DIR="$(cd "$(dirname "${
The changelog contains contradictory privacy claims: one entry says the raw log is 'never sent externally', while a later entry explicitly discloses that the raw pending log is included in prompts sent to the configured OpenClaw agent and may go off-host when using a remote/cloud provider. This is dangerous because operators may rely on the earlier statement and unknowingly expose sensitive operational logs, secrets, or incident data to third-party LLM infrastructure.
Without declared permissions the skill's intent is opaque and cannot be validated.
The skill explicitly transmits operational logs to Telegram/Discord and may also send raw log content to an OpenClaw agent that could be backed by a remote LLM. Even though this is documented functionality, it creates a real data exfiltration/privacy risk if logs contain secrets, tokens, internal paths, or sensitive operational events.
It may add `LLM_API_KEY=ollama` and `OPENCLAW_WORKSPACE=<path>` if missing.
### Network
- **Telegram:** `POST https://api.telegram.org/bot<token>/sendMessage`
- **Discord:** `POST <your webhook URL>`
- **OpenClaw agent:** `openclaw agent --agent <id> --message <prompt> --json` (local gateway call)
⚠️ The raw log is included in the prompt. If your agent uses a remote/cloud LLM, log content will be transmitted off-host. Use a local-only agent if log privacy is required.
The script sources a shared secrets file even though its documented function is only to deliver an already-prepared message. Sourcing an external shell file imports all variables and executes any shell code in that file, unnecessarily expanding the trust boundary and exposing unrelated credentials to this process.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
PAYLOAD="{\"chat_id\":\"$CHAT_ID\",\"text\":$TEXT,\"parse_mode\":\"Markdown\"}"
[[ -n "$THREAD_ID" ]] && \
PAYLOAD="{\"chat_id\":\"$CHAT_ID\",\"message_thread_id\":$THREAD_ID,\"text\":$TEXT,\"parse_mode\":\"Markdown\"}"
curl -sf -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" -d "$PAYLOAD" --max-time 15 > /dev/null \
&& g "✓ Delivered to Telegram" || { r "Telegram delivery failed."; exit 1; }
;;
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
PAYLOAD="{\"chat_id\":\"$CHAT_ID\",\"text\":$TEXT,\"parse_mode\":\"Markdown\"}"
[[ -n "$THREAD_ID" ]] && \
PAYLOAD="{\"chat_id\":\"$CHAT_ID\",\"message_thread_id\":$THREAD_ID,\"text\":$TEXT,\"parse_mode\":\"Markdown\"}"
curl -sf -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" -d "$PAYLOAD" --max-time 15 > /dev/null \
&& g "✓ Delivered to Telegram" || { r "Telegram delivery failed."; exit 1; }
;;
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
WEBHOOK="$(cfg_under 'discord' 'webhook_url')"
[[ -z "$WEBHOOK" ]] && WEBHOOK="${PULSE_DISCORD_WEBHOOK_URL:-}"
[[ -z "$WEBHOOK" ]] && { r "Discord: webhook_url not set."; exit 1; }
curl -sf -X POST "$WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"content\":$TEXT}" --max-time 15 > /dev/null \
&& g "✓ Delivered to Discord" || { r "Discord delivery failed."; exit 1; }
In manual mode, the script accepts --skill/--cron/--cmd and immediately rewrites the user's crontab via wire_job with no interactive confirmation or dry-run. That creates a real safety issue because a mistyped or maliciously supplied command becomes persistent scheduled execution, which is especially sensitive in a tool whose core purpose is cron wiring.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
fi
# ── Remove cron entry ─────────────────────────────────────────────────────────
EXISTING_CRON="$(crontab -l 2>/dev/null || true)"
if echo "$EXISTING_CRON" | grep -q "pulse-board:$SKILL_SAFE"; then
echo "$EXISTING_CRON" | grep -v "pulse-board:$SKILL_SAFE" | crontab -
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
fi
# ── Remove cron entry ─────────────────────────────────────────────────────────
EXISTING_CRON="$(crontab -l 2>/dev/null || true)"
if echo "$EXISTING_CRON" | grep -q "pulse-board:$SKILL_SAFE"; then
echo "$EXISTING_CRON" | grep -v "pulse-board:$SKILL_SAFE" | crontab -
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
fi
# ── Remove cron entry ─────────────────────────────────────────────────────────
EXISTING_CRON="$(crontab -l 2>/dev/null || true)"
if echo "$EXISTING_CRON" | grep -q "pulse-board:$SKILL_SAFE"; then
echo "$EXISTING_CRON" | grep -v "pulse-board:$SKILL_SAFE" | crontab -
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
fi
# ── Remove cron entry ─────────────────────────────────────────────────────────
EXISTING_CRON="$(crontab -l 2>/dev/null || true)"
if echo "$EXISTING_CRON" | grep -q "pulse-board:$SKILL_SAFE"; then
echo "$EXISTING_CRON" | grep -v "pulse-board:$SKILL_SAFE" | crontab -
No suspicious patterns detected.