Back to skill

Security audit

Memory Onboarding Wizard

Security checks for vulnerabilities and agentic risk

Overview

This memory setup skill mostly does what it advertises, but its default heartbeat file persistently tells the agent to check email and calendar without a clear opt-in.

Review the generated HEARTBEAT.md before enabling this skill in an agent with email or calendar tools. Remove or explicitly opt into any recurring checks, and run the wizard only in a workspace you control, since it writes persistent memory files that future sessions will read.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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 (3)

T02 · Agent Memory Poisoning

Warning
Location
scripts/memory-onboarding-wizard.py:77
Finding

Default heartbeat template persistently directs access to private services

Content
View full analysis
str: return """# HEARTBEAT.md — Periodic Checklist > The agent reads this file on every heartbeat poll. > Keep it SHORT — tokens are precious. Remove completed items. > Add time-sensitive reminders or recurring checks here. ## Active Checks (rotate through these 2-4x/day) - [ ] Email — any urgent unread messages? - [ ] Calendar — upcoming events in the next 24-48h? - [ ] Weather — relevant if your human might go out? ## Reminders *(Add one-off reminders here, remove once done)* ## Rules - If nothing needs attention → reply `HEARTBEAT_OK` - Don't check the same thing twice within 30 minutes - Late night (23:00–08:00): stay quiet unless urgent --- *Tip: Edit this file anytime to add reminders or tasks.* """ ``` ### Technical Analysis The Skill is presented primarily as a memory-system initializer, but its generated `HEARTBEAT.md` also contains persistent operational instructions directing the agent to inspect email, calendar, and weather services multiple times per day. The documentation states that the agent reads this file on every heartbeat poll. Consequently, these instructions survive the wizard process and can continue influencing later sessions. Email and calendar inspection are not necessary to create the memory files and can involve private information or external integrations. The template does not obtain explicit consent for these individual checks, define which accounts may be accessed, or constrain the permitted operations to read-only access. Whether the checks succeed depends on the tools and credentials available to the host agent. ### Attack Path 1. A user runs the memory onboarding wizard. 2. The wizard creates `HEARTBEAT.md` from the embedded template. 3. OpenClaw loads ...[truncated 831 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/memory-onboarding-wizard.py:124
Finding

Workspace file creation can follow symbolic links outside the selected directory

Content
View full analysis
bool: path = os.path.join(workspace, "MEMORY.md") if os.path.exists(path): skip(f"MEMORY.md already exists — keeping it") return False with open(path, "w") as f: f.write(memory_md_template(today)) created(f"Created MEMORY.md with starter template") return True def ensure_daily_note(workspace: str, today: str) -> bool: memory_dir = os.path.join(workspace, "memory") os.makedirs(memory_dir, exist_ok=True) path = os.path.join(memory_dir, f"{today}.md") if os.path.exists(path): skip(f"memory/{today}.md already exists — keeping it") return False with open(path, "w") as f: f.write(daily_note_template(today)) created(f"Created memory/{today}.md (today's daily note)") return True def ensure_heartbeat_md(workspace: str) -> bool: path = os.path.join(workspace, "HEARTBEAT.md") if os.path.exists(path): skip(f"HEARTBEAT.md already exists — keeping it") return False with open(path, "w") as f: f.write(heartbeat_md_template()) created(f"Created HEARTBEAT.md with starter checklist") return True ``` ```python def write_user_md(workspace: str, name: str, timezone_str: str, use_case: str, today: str) -> bool: path = os.path.join(workspace, "USER.md") if os.path.exists(path): # Offer to update if interactive, but don't overwrite blindly skip(f"USER.md already exists — keeping it (edit manually to update)") return False with open(path, "w") as f: f.write(user_md_template(name, timezone_str, use_case, today)) created(f"Created USER.md for {name}") return True ``` ### Technical Analysis The script constructs ou ...[truncated 2199 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/memory-onboarding-wizard.py:105
Finding

Unescaped multiline profile input can inject persistent agent instructions

Content
View full analysis
str: return f"""# USER.md — About Your Human - **Name:** {name} - **Timezone:** {timezone_str} - **Main Use Case:** {use_case} ## Notes *(Add personal preferences, context, and anything the agent should know here)* --- *Created by memory-onboarding-wizard on {today}* *Update this file anytime to help your agent serve you better.* """ ``` ```python def ask_user_questions(non_interactive: bool) -> tuple[str, str, str]: """Ask 3 quick questions. Returns (name, timezone, use_case).""" if non_interactive: return ("You", "UTC", "General AI assistance") print() print(bold(" 3 quick questions to personalize your agent:")) print() try: name = input(f" {cyan('1.')} What should your agent call you? [You]: ").strip() if not name: name = "You" tz = input(f" {cyan('2.')} Your timezone? (e.g. UTC, Europe/London, US/Eastern) [UTC]: ").strip() if not tz: tz = "UTC" use_case = input(f" {cyan('3.')} Main use case for your agent? (e.g. productivity, trading, coding): ").strip() if not use_case: use_case = "General AI assistance" except (EOFError, KeyboardInterrupt): print() warn("Non-interactive environment detected — using defaults") return ("You", "UTC", "General AI assistance") return name, tz, use_case ``` ```python def write_user_md(workspace: str, name: str, timezone_str: str, use_case: str, today: str) -> bool: path = os.path.join(workspace, "USER.md") if os.path.exists(path): # Offer to update if interactive, but don't overwrite blindly skip(f"USER.md already exi ...[truncated 2103 chars]
Remediation
View remediation
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill instructs the user to run a local Python script that creates and writes multiple files in the OpenClaw workspace, but the skill metadata does not declare any tool scope such as permissions or allowed-tools. That mismatch reduces transparency and reviewability: a consumer of the skill cannot easily see from the manifest that it performs file writes, which increases the risk of unintended or overbroad filesystem modification.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% 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.

Content

Scanner excerpt · scripts/memory-onboarding-wizard.py (reported line 210)May include surrounding context.

python
def write_user_md(workspace: str, name: str, timezone_str: str, use_case: str, today: str) -> bool:
    path = os.path.join(workspace, "USER.md")
    if os.path.exists(path):
        # Offer to update if interactive, but don't overwrite blindly
        skip(f"USER.md already exists — keeping it (edit manually to update)")
        return False
    with open(path, "w") as f: