T01 · Skill Instruction Hijacking
Warning
- Location
- engine.js:235
- Finding
- Untrusted API Data Is Propagated into a Privileged OpenClaw System Event<![CDATA[ ## Vulnerability Details **File Location**: `engine.js:15-24`, `engine.js:107-121`, and `engine.js:235-258` **Vulnerability Type**: Indirect instruction injection through externally controlled event content **Risk Level**: Medium ### Vulnerable Code ```js function parseTimeToDate(dateObj, hhmm) { const clean = String(hhmm).trim().replace(/\s*\(.+\)$/, ''); const [h, m] = clean.split(':').map((v) => parseInt(v, 10)); if (Number.isNaN(h) || Number.isNaN(m)) { throw new Error(`Format waktu tidak valid: ${hhmm}`); } const d = new Date(dateObj); d.setHours(h, m, 0, 0); return d; } ``` ```js function buildReminderMessage({ prayerLabel, prayerTime, locationName, isRamadan, quote, nowDate }) { const dateId = new Intl.DateTimeFormat('id-ID', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' }).format(nowDate); const ramadanBadge = isRamadan ? '🌙 Ramadan: Ya' : '🌙 Ramadan: Tidak'; return [ '🕌 *Z-Cloud Prayer Reminder*', `📍 ${locationName}`, `📅 ${dateId}`, '', `⏰ Waktu *${prayerLabel}* telah tiba (${prayerTime})`, ramadanBadge, '', '📖 *Quote Hari Ini*', `“${quote.text}”`, `— ${quote.source}` ].join('\n'); } ``` ```js for (const prayer of TARGET_PRAYERS) { const apiTime = timingsData.data?.timings?.[prayer.key]; if (!apiTime) continue; const triggerAt = parseTimeToDate(scheduleDate, apiTime); if (triggerAt <= new Date()) continue; const timeLabel = String(apiTime).replace(/\s*\(.+\)$/, ''); const quote = pickQuote(quotes); const message = buildReminderMessage({ prayerLabel: prayer.label, prayerTime: timeLabel, locationName, isRamadan, quote, nowDate: today }); const whenIso = toIsoWithOffset(triggerAt, timezone); const jobName = `prayer-${prayer.label.toLowerCase()}-${whenIso.slice(0, 10)}`; const result = dryRun ? { mode: 'dry-run', command: '(skip)', output: 'not executed' } : scheduleSystemEve ...[truncated 3303 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce a strict full-string format for every API-provided prayer time before parsing: ```js function parsePrayerTime(value) { const input = String(value).trim(); const match = /^(?:([01]\d|2[0-3])):([0-5]\d)(?:\s+\([^)]+\))?$/.exec(input); if (!match) { throw new Error('Invalid prayer-time format received from API'); } return { hour: Number(match[1]), minute: Number(match[2]), label: `${match[1]}:${match[2]}` }; } ``` 2. Reconstruct the displayed time exclusively from validated hour and minute components. Do not reuse the original API string in an agent-facing message. 3. Explicitly reject control characters, including carriage returns, newlines, tabs, null bytes, and other nonprinting characters, in all externally sourced fields. 4. Treat API responses as untrusted data even when delivered over HTTPS. Validate the expected schema, data types, ranges, and string lengths before use. 5. Prefer a plain notification or data-only channel instead of a system-event channel when the content does not need to act as an agent instruction. 6. If system events are required, use a fixed trusted instruction template and place external values in a clearly delimited data structure that the receiving agent is instructed not to interpret as commands. 7. Add regression tests containing numeric-prefix payloads, embedded newlines, Unicode control characters, excessively long values, invalid hours, and invalid minutes. ]]>
