T02 · Agent Memory Poisoning
Warning
- Location
- scripts/monitor-error.js:86
- Finding
- Untrusted log content is written into persistent workspace memory without sanitization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/monitor-error.js`, lines 86-91 and 189-201 **Vulnerability Type**: Persistent Markdown injection / memory poisoning **Risk Level**: Medium ### Vulnerable Code ```javascript for (const key of Object.keys(obj)) { if (key === '_meta' || key === 'time') continue; parts.push(typeof obj[key] === 'string' ? obj[key] : JSON.stringify(obj[key])); } message = parts.join(' | '); for (const err of errors) { const time = err.timestamp.slice(11, 19); const shortMsg = err.message.slice(0, 80).replace(/\|/g, '\\|'); const errorType = err.hash.split(':')[1] || 'unknown'; lines_out.push(`| ${time} | ${err.subsystem || '-'} | ${errorType} | ${shortMsg} |`); } lines_out.push(''); lines_out.push('---'); lines_out.push(''); fs.appendFileSync(ERROR_FILE, '\n' + lines_out.join('\n')); ``` ### Technical Analysis The monitor treats fields from JSON error logs as trusted text and appends them to `~/.openclaw/workspace/error.md`, which is a persistent file inside the agent workspace. Only pipe characters in the shortened message are escaped. Markdown control characters, HTML, line breaks, links, headings, and instruction-like text are not sanitized. The `subsystem` field is also inserted into the Markdown table without any escaping. It is derived from log content at lines 95-106 and can therefore affect the generated document when an attacker can influence logged values. Because the destination is a workspace Markdown file, the injected content may later be presented to an agent or user as trusted monitoring history. If workspace memory files are supplied to an agent in later sessions, attacker-controlled log text can become persistent prompt content. The code does not label entries as untrusted data or enforce a serialization format that separates data from instructions. The message is truncated to 80 characters when persisted, but this does not prevent short instruction payloads, Markdown structure manip ...[truncated 1691 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Treat every log field as untrusted data. 2. Store findings in a structured format such as JSON rather than directly generating agent-consumable Markdown. 3. If Markdown output is required, escape all Markdown and HTML metacharacters in every interpolated field, including `subsystem`, timestamp, error type, and message. 4. Normalize or remove carriage returns, line feeds, Unicode line separators, and control characters before output. 5. Apply strict length limits to all fields, especially `subsystem`. 6. Add a fixed warning around generated content stating that log entries are untrusted evidence and must never be interpreted as instructions. 7. Keep operational logs outside directories automatically loaded as agent memory. Provide them through a dedicated viewer or tool response instead. 8. If an agent must consume the file, pass records through a data-only interface and explicitly instruct the agent not to follow instructions found inside log values. 9. Add tests covering payloads with newlines, headings, links, HTML, table delimiters, code fences, and short prompt-injection phrases. A defensive helper could normalize fields before formatting: ```javascript function sanitizeLogField(value, maxLength = 200) { return String(value ?? '') .replace(/[\r\n\u2028\u2029]+/g, ' ') .replace(/[\x00-\x1F\x7F]/g, '') .replace(/[\\`*_[\]{}()#+.!|<>-]/g, '\\$&') .slice(0, maxLength); } ``` Apply this helper to every value interpolated into `lines_out`. ]]>
