Back to skill

Security audit

smart-keepalive

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated scheduled brief-sending purpose, but it needs review because it can run unattended message delivery while allowing broad URL overrides and custom shell commands.

Review this before installing if it will run unattended. Confirm the message channel and target, avoid custom shell commands unless they are trusted and audited, do not point RSS or weather URL overrides at internal or sensitive hosts, and consider disabling scheduled sending until you have reviewed logs and the generated messages.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
smart-keepalive.py:2188
Finding
Untrusted feed content is inserted directly into an Agent prompt<![CDATA[ ## Vulnerability Details **File Location**: `smart-keepalive.py:2188-2219` **Related Source Ingestion**: `smart-keepalive.py:526-558` **Vulnerability Type**: Indirect prompt injection through externally controlled feed content **Risk Level**: Medium ### Vulnerable Code ```python def generate_message( openclaw_bin: str, locale: str, skill_dir: Path, agent_id: str ) -> tuple[str, str]: brief, theme_tag = build_keepalive_brief(with_links=True, skill_dir=skill_dir) weather_city = resolve_weather_city(skill_dir) if weather_append_enabled(): weather_line = fetch_nmc_weather_daily(weather_city, 1, with_links=True) if weather_line.strip(): brief = f"{brief}\n{weather_line}" now = datetime.now() style = os.getenv("KEEPALIVE_STYLE_GUIDE", "").strip() or "(无)" tpl = load_prompt_file(skill_dir, "rewrite-main.md") if not tpl.strip(): tpl = FALLBACK_REWRITE_PROMPT prompt = fill_prompt_template( tpl, { "LOCALE": locale, "BRIEF": brief, "HOUR": str(now.hour), "MINUTE": str(now.minute), "LOCAL_TIME": now.strftime("%Y-%m-%d %H:%M:%S"), "STYLE_GUIDE": style, "THEME_HINT": theme_section_label(locale, theme_tag), "WEATHER_CITY": weather_city, }, ) code, out = run_agent_command( openclaw_bin=openclaw_bin, agent_id=agent_id, prompt=prompt, timeout_sec=45, ) ``` The content placed in `brief` originates from external RSS and HTTP responses: ```python req = urllib.request.Request( safe_url, headers={ "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ), }, ) with urllib.request.urlopen(req, timeout=12) as resp: data = resp.read() root = ET.fromstring(data) items = root.findall(".//item") if not i ...[truncated 2556 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Represent source records as structured objects containing separate `title`, `url`, and `source` fields instead of concatenating them into free-form prompt text. 2. Serialize those records as JSON inside a clearly delimited data block. 3. Add an explicit high-priority instruction stating that all text inside the source-data block is untrusted content and that directives found there must never be followed. 4. Validate Agent output against a strict schema before sending it: - Require the fixed report heading. - Limit the number and length of entries. - Reject unexpected sections or instructions. - Ensure each visible title exactly matches a parsed source title. - Ensure every URL exactly matches a URL from the corresponding parsed record. 5. Build the final Markdown links in Python from validated source records rather than allowing the Agent to create arbitrary links. 6. Remove control characters and impose conservative byte and character limits on titles and URLs. 7. Run the rewrite Agent without external tools, sensitive session context, or filesystem/network permissions where the runtime supports such isolation. 8. If validation fails, use the deterministic Python formatter instead of transmitting the unvalidated Agent response. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
smart-keepalive.py:592
Finding
Configurable feed and weather URLs allow requests to arbitrary hosts<![CDATA[ ## Vulnerability Details **File Location**: `smart-keepalive.py:592-620` **Additional Locations**: `smart-keepalive.py:753-767`, `smart-keepalive.py:802-811`, `smart-keepalive.py:849-866` **Vulnerability Type**: Server-side request forgery through unrestricted URL overrides **Risk Level**: Medium ### Vulnerable Code The RSSHub base URL is taken directly from an environment variable: ```python def _normalize_rsshub_base(base: str) -> str: b = (base or "").strip().rstrip("/") if not b: return "" if not (b.startswith("http://") or b.startswith("https://")): b = "https://" + b return b def rsshub_bases() -> list[str]: """RSSHub root address list; environment variable takes precedence.""" raw = os.getenv("KEEPALIVE_RSSHUB_BASES", "").strip() if raw: out = [_normalize_rsshub_base(x) for x in raw.split(",")] return [x for x in out if x] return [_normalize_rsshub_base(x) for x in DEFAULT_RSSHUB_BASES if x] def fetch_rsshub_route( route: str, count: int = 5, with_links: bool = False ) -> str: """Try multiple RSSHub instances in sequence.""" path = (route or "").strip().strip("/") if not path: return "" for base in rsshub_bases(): url = f"{base}/{path}" got = fetch_rss_titles(url, count, max_retries=2, with_links=with_links) if got.strip(): return got return "" ``` The weather override similarly controls both the page request and the origin used for REST requests: ```python def _nmc_base_url() -> str: """Derive the API base origin from the weather URL.""" import urllib.parse raw = os.getenv("KEEPALIVE_NMC_WEATHER_URL", "").strip() or DEFAULT_NMC_WEATHER_DAILY_URL try: u = urllib.parse.urlsplit(raw) if u.scheme and u.netloc: return f"{u.scheme}://{u.netloc}" except Exception: pass return "https://www.nmc.cn" ``` ```python def fetch_nmc_weather_daily(city: str, ...[truncated 3836 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for all configurable remote sources. 2. Maintain an explicit allowlist of approved RSSHub and weather domains. If mirrors are required, make the allowlist configurable only through a trusted local configuration file with restrictive permissions. 3. Before connecting, resolve the hostname and reject every address in loopback, private, link-local, multicast, reserved, unspecified, or documentation-only ranges for both IPv4 and IPv6. 4. Protect against DNS rebinding by connecting only to a validated resolved address while preserving certificate and hostname verification. 5. Disable automatic redirects or validate every redirect target with the same scheme, hostname, DNS, and IP-address rules. 6. Restrict the weather override to known NMC-compatible origins rather than deriving REST origins from any supplied URL. 7. Apply response-size limits before reading response bodies to reduce denial-of-service exposure. 8. Run scheduled fetches in a network sandbox that permits access only to the approved public endpoints. 9. Ensure cron or launchd environment configuration is writable only by the owning user and does not inherit variables from untrusted files or services. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (58)

Tainted flow: 'req' from os.getenv (line 549, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
),
                },
            )
            with urllib.request.urlopen(req, timeout=12) as resp:
                data = resp.read()
            root = ET.fromstring(data)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 549, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode('utf-8'))
        
        if data.get('code') != 0:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 549, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"Pragma": "no-cache",
        },
    )
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8", errors="ignore"))
Confidence
91% confidence
Finding
The code builds outbound HTTP requests from a base URL derived from the KEEPALIVE_NMC_WEATHER_URL environment variable, then fetches JSON from that host. Because the host is environment-controlled and only minimally constrained, a user or upstream scheduler can redirect requests to arbitrary internal or attacker-controlled endpoints, creating SSRF-style behavior and external data exfiltration risk.

Tainted flow: 'req' from os.getenv (line 549, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"Pragma": "no-cache",
            },
        )
        with urllib.request.urlopen(req, timeout=12) as resp:
            html_text = resp.read().decode("utf-8", errors="ignore")
    except Exception:
        return ""
Confidence
94% confidence
Finding
fetch_nmc_weather_daily reads KEEPALIVE_NMC_WEATHER_URL from the environment and performs a direct urlopen to that URL. In an agent/scheduled-skill context, environment-controlled network destinations are dangerous because they can be abused to contact arbitrary hosts, including internal services, and influence content inserted into outbound messages.

Ae1

High
Category
analysis-evasion
Content
**何时读本文:** 用户要配置/调试 OpenClaw **定时 keepalive**、飞书或微信定时消息、`smart-keepalive.py` / `smart-keepalive.sh`、资讯简报、`prompts/`、`--doctor` / `--install-launchd` / `--instal
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**何时读本文:** 用户要配置/调试 OpenClaw **定时 keepalive**、飞书或微信定时消息、`smart-keepalive.py` / `smart-keepalive.sh`、资讯简报、`prompts/`、`--doctor` / `--install-launchd` / `--instal
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**何时读本文:** 用户要配置/调试 OpenClaw **定时 keepalive**、飞书或微信定时消息、`smart-keepalive.py` / `smart-keepalive.sh`、资讯简报、`prompts/`、`--doctor` / `--install-launchd` / `--instal
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**何时读本文:** 用户要配置/调试 OpenClaw **定时 keepalive**、飞书或微信定时消息、`smart-keepalive.py` / `smart-keepalive.sh`、资讯简报、`prompts/`、`--doctor` / `--install-launchd` / `--instal
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **管线顺序、环境变量、`theme_tag` 含义、基准权重数字、状态 JSON 字段** | **`SKILL.md`(本文)** | 给人与 Agent 的**单一事实说明**;改脚本逻辑后**同步改本节**,避免脚本顶部长注释与文档漂移。 |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **管线顺序、环境变量、`theme_tag` 含义、基准权重数字、状态 JSON 字段** | **`SKILL.md`(本文)** | 给人与 Agent 的**单一事实说明**;改脚本逻辑后**同步改本节**,避免脚本顶部长注释与文档漂移。 |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Env Variable Harvesting

High
Category
Data Exfiltration
Content
cwd: Optional[Path] = None,
    extra_env: Optional[dict[str, str]] = None,
) -> tuple[int, str]:
    env = os.environ.copy()
    if extra_env:
        env.update(extra_env)
    return run_cmd(["/bin/sh", "-lc", command], timeout_sec=timeout_sec, cwd=cwd, env=env)
Confidence
97% confidence
Finding
run_shell_command copies the full process environment and then invokes a shell command hook. In practice this can expose tokens, credentials, and internal configuration to any custom command, making the custom hook much more dangerous than a minimal, purpose-built subprocess call.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
],
        timeout_sec=timeout_sec,
        cwd=None,
        env={k: v for k, v in os.environ.items() if k not in ["SKILL_DIR", "OPENCLAW_HOME", "OPENCLAW_CONFIG"]},
    )
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
text=True,
            timeout=timeout_sec,
            cwd=str(cwd) if cwd else None,
            env=env if env is not None else os.environ.copy(),
        )
        return p.returncode, (p.stdout or "") + (p.stderr or "")
    except subprocess.TimeoutExpired as ex:
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def merge_plist_env_into_environ(plist_path: Path) -> dict[str, str]:
    """将 launchd plist 中的 EnvironmentVariables 合并进当前环境,用于模拟定时任务环境。"""
    merged: dict[str, str] = dict(os.environ)
    if not plist_path.is_file():
        return merged
    try:
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def merge_plist_env_into_environ(plist_path: Path) -> dict[str, str]:
    """将 launchd plist 中的 EnvironmentVariables 合并进当前环境,用于模拟定时任务环境。"""
    merged: dict[str, str] = dict(os.environ)
    if not plist_path.is_file():
        return merged
    try:
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill explicitly supports periodic unattended fetching and automatic message sending, including via custom commands, but the documentation does not prominently warn users that content will be transmitted over the network on a schedule. In a scheduled automation context, users may unknowingly exfiltrate message content, prompts, targets, or fetched material to external services without realizing the privacy and operational implications.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The Hermes/custom-command integration instructs users to set shell command strings that consume sensitive environment variables such as KEEPALIVE_PROMPT, KEEPALIVE_MESSAGE, KEEPALIVE_CHANNEL, and KEEPALIVE_TARGET, but it does not clearly warn that these commands execute arbitrary user-configured shell/CLI actions and can forward data to external tools or endpoints. In practice, this expands the trust boundary significantly and can lead to unreviewed exfiltration of message contents or prompt data, especially in unattended scheduled runs.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The prompt mandates that when `locale={{LOCALE}}`, `zh` must use the Chinese-format second paragraph and `en` must produce the body in English. This enforces a language choice from configuration rather than offering the user a language/locale option or documenting explicit opt-in.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The prompt explicitly maps every locale except `en` to Chinese, which overrides the user's actual language preference for all other supported locales. This can cause incorrect or misleading output in multilingual environments and may break downstream workflows or user expectations that depend on locale-appropriate messaging.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This instruction hard-codes a language policy: English is allowed only when `{{LOCALE}}` equals `en`, and all other cases must be Chinese. That creates a locale restriction without offering the user a language choice or documenting a justified region-specific constraint.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
import json
import os
import plistlib
import re
import shutil
import subprocess
Confidence
75% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
import json
import os
import plistlib
import re
import shutil
import subprocess
Confidence
75% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
import json
import os
import plistlib
import re
import shutil
import subprocess
Confidence
75% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
import json
import os
import plistlib
import re
import shutil
import subprocess
Confidence
75% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env python3
import json
import os
import plistlib
import re
import shutil
import subprocess
Confidence
75% confidence
Finding
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.

Static analysis

No suspicious patterns detected.