T09 · Insecure Skill Coding Practices
- Location
scripts/setup.py:286- Finding
Shell Command Injection Through Unsafe Cron Command Construction
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This memory skill is purpose-aligned, but it needs Review because it installs persistent agent jobs, modifies long-term memory, uses unsafe shell command construction, and encourages retaining sensitive data including credentials.
Install only after reviewing the setup script and cron prompts. Do not run it with --all on shared or multi-agent systems unless you intend to modify every discovered agent. Avoid storing passwords, API keys, tokens, private keys, addresses, or other sensitive personal data in these memory files, and prefer running --dry-run first. The shell command construction should be fixed before use in an environment where agent IDs, timezones, prompt files, PATH, or local files could be influenced by another user or process.
scripts/setup.py:286Shell Command Injection Through Unsafe Cron Command Construction
scripts/setup.py:238Installation of Persistent Scheduled Agent Tasks
SKILL.md:136Plaintext Long-Term Retention and Duplication of Credentials and Personal Data
The skill claims to implement a lifecycle for setup and ongoing memory management, but the documented behavior relies on external scripts/prompts and introduces undeclared operational dependencies such as cron-based automation. This mismatch can mislead operators and agents about what the skill actually does, causing unsafe assumptions, missed controls, or unexpected execution paths.
The skill states daily files are immutable and should never be edited, yet later instructs adding headers and weekly reviews to those same files. This contradiction is dangerous because it undermines audit integrity and can cause agents or users to trust records as immutable when they are not.
Within the nightly cycle, the instructions both prohibit editing daily file content and require writing a 'day essence' header into the daily file. This internal conflict can lead to unauthorized modifications of records and weakens any audit or provenance guarantees the memory system is supposed to provide.
This is a tool-parameter abuse risk because the script executes an external tool through the shell, inheriting ambient environment state and trusting command resolution. In an agent or automation context, that increases danger: if PATH, shell startup behavior, or the openclaw executable is attacker-influenced, the health check can execute unintended code with the script's privileges.
def check_nightly_cron(agent_id):
"""Check if the nightly cron job ran successfully."""
result = subprocess.run(
f'openclaw cron list --json 2>/dev/null',
shell=True, capture_output=True, text=True
)
This is the core command-injection issue: a shell is invoked on a composed command string that includes externally influenced data. Because the script inlines prompt-file contents into a double-quoted --message argument, any embedded quotes, command substitutions, or shell metacharacters in those files or parameters can escape argument boundaries and execute arbitrary host commands.
if dry_run:
print(f" [DRY RUN] {cmd}")
return None
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f" [ERROR] {cmd}")
print(f" stderr: {result.stderr.strip()}")
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
def discover_agents():
"""Discover all agents on this OpenClaw server."""
result = subprocess.run("openclaw status --json 2>/dev/null", shell=True, capture_output=True, text=True)
try:
data = json.loads(result.stdout)
agents_data = data.get("agents", {})
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
def get_existing_cron_jobs(agent_id):
"""Fetch existing cron jobs for this agent."""
result = subprocess.run(
f'openclaw cron list --json 2>/dev/null',
shell=True, capture_output=True, text=True
)
The skill instructs execution of local scripts and implies file read/write and shell-capable behavior, but it does not declare any explicit tool scope or permissions. That creates ambiguity about what the skill is allowed to modify and increases the chance an agent will perform filesystem or shell actions without adequate user awareness or policy gating.
The trigger phrases are broad enough to activate on common, non-administrative conversations about 'memory' or 'improvement.' In context, this is risky because the skill can lead to file modifications and scheduler setup, so accidental invocation could escalate a casual request into system-changing behavior.
The skill directs creation of persistent cron jobs, which is a system-level persistence mechanism. Even if intended for benign automation, persistent scheduled tasks expand the skill's operational reach beyond simple memory organization and can be abused to establish unattended recurring execution.
The setup instructions tell users to run a script that creates files and cron jobs without a clear upfront warning that it modifies the local system and installs persistence. Users or agents may execute it assuming it is a harmless organizational step, leading to unintended changes to the host environment.
The skill explicitly recommends retaining highly sensitive data such as phone numbers, addresses, credentials, family details, and business structure in long-term memory without minimization or protection guidance. In a persistent agent memory context, this increases the blast radius of compromise, accidental disclosure, over-retention, and privacy/security violations.
The prompt explicitly instructs the agent to archive full narratives containing contact information, prices, dates, and reference numbers, which can include personal or otherwise sensitive data. Because this is a memory-management skill for long-running agents, the instruction normalizes broad retention and duplication of sensitive information without any minimization, consent check, access-control guidance, or warning to the user.
The file directs the agent to create self-contained archive records with detailed narratives that include who was involved and specific operational details, increasing the amount and persistence of sensitive data in one place. Self-contained archives are convenient, but they also concentrate personal and transactional information, raising privacy, over-retention, and unintended disclosure risk if the archive is accessed, shared, or repurposed.
The rule says to preserve phone numbers, dates, prices, and reference numbers as 'facts, not fluff,' which directly discourages minimization of potentially sensitive personal and financial data. In the context of an agent memory system, this increases the chance of long-term retention, duplication, and later leakage of information that may not be necessary for future operation.
The prompt explicitly instructs the agent to modify and delete memory content, including clearing the ## Recent section in MEMORY.md, but provides no user-facing confirmation, backup step, or safety check before destructive changes. In a long-running agent memory system, these actions can silently remove context that was misfiled, incompletely archived, or still needed, causing loss of traceability and degraded future behavior.
The prompt instructs the agent to create a permanent yearly file and explicitly says it is never edited after creation, but it does not require any user-facing notice, confirmation, or retention disclosure before writing durable memory. In a long-running agent memory system, this can cause silent persistence of sensitive personal, identity, relationship, or decision-history data in a way the user may not expect or be able to easily correct later.
The code invokes a shell via subprocess.run(..., shell=True) even though the command is static. Using a shell is dangerous because execution depends on the runtime environment and PATH resolution, so a malicious or unintended openclaw binary or shell behavior could be triggered if the environment is compromised.
def check_nightly_cron(agent_id):
"""Check if the nightly cron job ran successfully."""
result = subprocess.run(
f'openclaw cron list --json 2>/dev/null',
shell=True, capture_output=True, text=True
)
This helper executes a dynamically constructed shell command with shell=True. The command string includes attacker-influenced values later in the script, such as agent_id, timezone, descriptions, and especially prompt text embedded into the --message argument, so crafted quotes or shell metacharacters could break out of the intended command and execute arbitrary commands on the host.
if dry_run:
print(f" [DRY RUN] {cmd}")
return None
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f" [ERROR] {cmd}")
print(f" stderr: {result.stderr.strip()}")
The script enumerates all agents and their workspace paths from the server, revealing inventory and filesystem layout information unrelated to normal single-agent setup. In a multi-tenant or shared deployment, that metadata exposure increases reconnaissance value and enables broader targeting of other agents' data.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def discover_agents():
"""Discover all agents on this OpenClaw server."""
result = subprocess.run("openclaw status --json 2>/dev/null", shell=True, capture_output=True, text=True)
try:
data = json.loads(result.stdout)
agents_data = data.get("agents", {})
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def get_existing_cron_jobs(agent_id):
"""Fetch existing cron jobs for this agent."""
result = subprocess.run(
f'openclaw cron list --json 2>/dev/null',
shell=True, capture_output=True, text=True
)
This code creates persistent cron jobs that will continue to execute after the initial setup, but it does so without a strong warning or confirmation at runtime. In security terms, scheduler-state changes are persistence mechanisms, so silently installing them increases operational risk, especially when the jobs act on memory files over time.
The --all mode lets this setup script modify every discovered agent on the server, creating files and scheduler entries across multiple workspaces. For a memory-management skill, that expands the blast radius far beyond a single requested agent and could cause unauthorized cross-agent changes or persistence if run in a shared environment.
The script performs multiple filesystem writes and modifications within the workspace automatically, including creating memory files and editing MEMORY.md and HEARTBEAT.md, without an execution-time confirmation prompt. In an agent-skill context, silent broad writes are risky because they can alter user data or configuration unexpectedly and make misuse harder to detect before changes are committed.