Messages from /hooks/agent containing SECURITY NOTICE blocks and <<<EXTERNAL_UNTRUSTED_CONTENT>>> delimiters are now cleaned up. Only the actual user content is displayed, with a small webhook badge indicator showing the message originated from a webhook. Closes feedback #54
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
/**
|
|
* Detect whether a user-role message is actually a system event
|
|
* (heartbeat, webhook, cron, channel event, etc.) rather than
|
|
* a real human message.
|
|
*/
|
|
|
|
const SYSTEM_PATTERNS: RegExp[] = [
|
|
// Explicit markers
|
|
/^\[EVENT\b/i,
|
|
/\[from:\s*[^\]]*\(system\)\]/i,
|
|
/^\[HEARTBEAT\b/i,
|
|
/^\[cron:/i,
|
|
/^\[hook:/i,
|
|
/^\[webhook:/i,
|
|
/^\[sms-inbound\b/i,
|
|
/^\[teamspeak\b/i,
|
|
|
|
// Heartbeat prompt pattern (the standard OpenClaw heartbeat)
|
|
/^Read HEARTBEAT\.md if it exists/,
|
|
|
|
// System event envelope: [source:xxx]
|
|
/^\[source:\s*\w+\]/i,
|
|
];
|
|
|
|
export function isSystemEvent(text: string): boolean {
|
|
const trimmed = text.trim();
|
|
if (!trimmed) return false;
|
|
return SYSTEM_PATTERNS.some(pat => pat.test(trimmed));
|
|
}
|
|
|
|
/**
|
|
* Strip webhook/hook scaffolding from message content.
|
|
* OpenClaw wraps inbound webhook payloads in security envelopes like:
|
|
* [hook:agent task_id=xxx job_id=xxx]
|
|
* --- SECURITY NOTICE ---
|
|
* ...
|
|
* <<<EXTERNAL_UNTRUSTED_CONTENT>>>
|
|
* actual message
|
|
* <<<END_EXTERNAL_UNTRUSTED_CONTENT>>>
|
|
*
|
|
* This extracts the actual user content and returns it clean.
|
|
* Also strips leading [hook:...] / [cron:...] / [sms-inbound ...] tags
|
|
* and SECURITY NOTICE blocks when no EXTERNAL_UNTRUSTED_CONTENT delimiters exist.
|
|
*/
|
|
export function stripWebhookScaffolding(text: string): string {
|
|
const trimmed = text.trim();
|
|
|
|
// Extract content between <<<EXTERNAL_UNTRUSTED_CONTENT>>> delimiters
|
|
const extMatch = trimmed.match(
|
|
/<<<EXTERNAL_UNTRUSTED_CONTENT>>>\s*([\s\S]*?)\s*<<<END_EXTERNAL_UNTRUSTED_CONTENT>>>/
|
|
);
|
|
if (extMatch) {
|
|
return extMatch[1].trim();
|
|
}
|
|
|
|
// Strip leading bracket tags: [hook:...], [cron:...], [sms-inbound ...], etc.
|
|
let cleaned = trimmed.replace(/^\[(?:hook|cron|webhook|sms-inbound)[^\]]*\]\s*/i, '');
|
|
|
|
// Strip SECURITY NOTICE blocks (--- SECURITY NOTICE --- ... --- END ---)
|
|
cleaned = cleaned.replace(
|
|
/---\s*SECURITY NOTICE\s*---[\s\S]*?---\s*END\s*---\s*/i,
|
|
''
|
|
);
|
|
|
|
// Strip standalone security notice lines without END marker
|
|
cleaned = cleaned.replace(
|
|
/---\s*SECURITY NOTICE\s*---[^\n]*\n(?:.*\n)*?(?=\S)/i,
|
|
''
|
|
);
|
|
|
|
// Strip task/job ID lines
|
|
cleaned = cleaned.replace(/^(?:task_id|job_id|Task|Job)\s*[:=]\s*\S+\s*\n?/gim, '');
|
|
|
|
return cleaned.trim() || trimmed;
|
|
}
|
|
|
|
/**
|
|
* Check if a message contains webhook scaffolding that should be cleaned.
|
|
*/
|
|
export function hasWebhookScaffolding(text: string): boolean {
|
|
return /<<<EXTERNAL_UNTRUSTED_CONTENT>>>/.test(text) ||
|
|
/---\s*SECURITY NOTICE\s*---/i.test(text);
|
|
}
|