T09 · Insecure Skill Coding Practices
- Location
scripts/update_vitamin_crons.py:67- Finding
Persistent Indirect Prompt Injection Through Untrusted Reminder Content
- Content
View full analysis
Vulnerability Details
File Location:
scripts/update_vitamin_crons.py, lines 67–84
Vulnerability Type: Persistent indirect prompt injection caused by embedding untrusted file content in an agent instruction
Risk Level: MediumVulnerable Code
python def ensure_job(label, hhmm, items, channel_id): slug = label.lower().replace(' ', '-') name = f'vitamins-{slug}-reminder' expr = time_to_cron(hhmm) reminder_text = build_message(label, items) session_key = SESSION_KEY_TEMPLATE.format(channel_id=channel_id) return run_openclaw([ 'add', '--name', name, '--session-key', session_key, '--cron', expr, '--tz', TZ, '--message', f'Send a brief reminder to the user in the bound session: "{reminder_text}"', '--timeout-seconds', '120', '--announce', '--channel', 'discord', '--to', channel_id, '--json', ])Technical Analysis
The
labelanditemsvalues originate from the user-controlled$WORKSPACE/VITAMINS.mdfile.build_message()combines these values intoreminder_text, which is then embedded verbatim in the--messageargument supplied to an OpenClaw cron job.Although
reminder_textis surrounded by quotation marks, quotation marks do not create a security boundary in a natural-language agent prompt. A malicious label or supplement entry can contain instructions directing the receiving agent to ignore the intended reminder task and perform unrelated actions. The resulting instruction is stored as a cron job and is delivered repeatedly to the bound Discord agent session.This is not operating-system shell injection because
subprocess.run()receives an argument list and does not enable a shell. The vulnerability instead occurs at the agent instruction boundary: attacker-controlled data and trusted operational instructions are combined in the same natural-lang ...[truncated 1919 chars]- Remediation
View remediation
Remediation Suggestions
-
Avoid invoking an agent for literal reminders. Prefer a cron delivery mechanism that posts a fixed, literal message directly to the destination without asking a language model or autonomous agent to interpret it.
-
Separate instructions from untrusted data. If agent execution is unavoidable, transmit labels and supplement items through typed or structured fields and configure the receiving component to treat those fields exclusively as data.
-
Apply strict input validation.
- Restrict labels to a conservative character allowlist.
- Restrict supplement names to expected printable characters.
- Reject line breaks, control characters, quotation-based prompt delimiters, and instruction-like structural content.
- Enforce reasonable length limits on labels, individual items, and the complete reminder.
- Validate schedule times as real values from
00:00through23:59.
-
Use an explicit non-execution template. If natural-language delivery remains necessary, state that the enclosed content is untrusted display data and must not be interpreted as instructions. This is defense in depth and should not replace direct literal delivery or structural isolation.
-
Constrain the cron agent. Bind reminder jobs to an identity with no file, shell, administrative, secret-access, or unrelated messaging capabilities. Limit it to posting in the explicitly configured destination.
-
Protect the input file. Apply restrictive filesystem permissions to
$WORKSPACE/VITAMINS.mdand its parent directory so that only the intended account can modify the schedule. -
Review generated jobs before activation. Display the final message and require confirmation when content contains unexpected punctuation, excessive length, multiline text, or instruction-like phrases.
-
