April 17, 2026

Discord-Kanäle leiten interne Payloads weiter - Discord Channel Leaking Internal Payloads: EXTERNAL_UNTRUSTED_CONTENT Wrappers in User Messages

Interne Wrapper-Marker und fehlerhaft extrahierte Anhangstexte werden an Discord-Kanäle weitergeleitet, anstatt vor der Übertragung bereinigt zu werden.

🔍 Symptome

Beobachtete benutzerseitige Fehler

Bei der Interaktion mit dem Assistenten über Discord beobachten Benutzer Nachrichten, die rohe interne Inhalte enthalten, die niemals die Präsentationsebene erreichen sollten. Die offengelegten Inhalte manifestieren sich in zwei unterschiedlichen Mustern:

Muster 1: Wrapper-Syntax-Leckage

Nachrichten enthalten rohe Serialisierungsmarker direkt im Discord-Chat:

<<<EXTERNAL_UNTRUSTED_CONTENT id="msg_abc123">>>
Source: External
UNTRUSTED Discord message body
<<<END_EXTERNAL_UNTRUSTED_CONTENT id="msg_abc123">>>

Muster 2: Spam mit malformed Anhang-Payload

Große Textblöcke mit unsinnigem Inhalt, dominiert von wiederholten technischen Begriffen:

attach attachment attachment hookup toggle compiler 
attachment hookup toggle compiler attach attachment 
UNTRUSTED Discord message body Source External Source External
attach attachment attachment hookup toggle compiler

Technische Manifestationen

KomponenteManifestation
Discord-TransportRohe Wrapper-Tags erscheinen in ausgehenden Nachrichten-Payloads
Anhang-HandlerKorrupte Extraktionsergebnisse werden an den Kanal weitergeleitet
Asynchrone Tool-VervollständigungEingereihte Vervollständigungstexte enthalten interne Marker
SanitisierungsschichtGrenzendurchsetzungsfehler zwischen Kontext und Rendering

Auslösebedingungen

Das Problem tritt nach einer der folgenden Operationen auf:

  • Der Assistent verarbeitet eine Nachricht mit Anhängen
  • Asynchrone Tool-Vervollständigung liefert Ergebnisse an den Discord-Kanal
  • Externe Inhalte werden durch das EXTERNAL_UNTRUSTED_CONTENT-Wrapper-System verarbeitet
  • Mehrfach-Gespräch mit Datei-/Bildanhängen

🧠 Ursache

Architektonische Fehlerpunkte

Die Leckage deutet auf einen Sanitisierungsgrenzen-Fehler in der Nachrichtenpipeline zwischen interner Verarbeitung und Discord-Transport hin. Das OpenClaw-Framework verwendet den EXTERNAL_UNTRUSTED_CONTENT-Wrapper, um nicht vertrauenswürdige Benutzerinhalte während der Agent-Verarbeitung zu isolieren. Dieser Wrapper sollte:

  1. Intern während der Kontextzusammenstellung verbraucht werden
  2. Niemals an ausgehende Transport-Schichten serialisiert werden
  3. Entfernt werden, bevor eine Nachricht die Rendering-Pipeline erreicht

Fehlersequenz

┌─────────────────────────────────────────────────────────────────┐
│                    MESSAGE FLOW (FAILING)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Discord Message Received                                        │
│         │                                                        │
│         ▼                                                        │
│  ┌─────────────────┐                                            │
│  │ Content Wrapper │  ← EXTERNAL_UNTRUSTED_CONTENT added        │
│  │   Injection     │     to isolate untrusted input              │
│  └────────┬────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────┐                                            │
│  │  Agent Runtime   │  ← Wrapper consumed in context              │
│  │   Processing     │     (intended behavior)                    │
│  └────────┬────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────┐                                            │
│  │ Discord Transport│ ← SANITIZATION FAILURE                     │
│  │   Renderer       │   Wrapper not stripped before posting       │
│  └────────┬────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────┐                                            │
│  │  RAW WRAPPER +  │  ← User sees:                               │
│  │   Payload        │     <<>>   │
│  │   Forwarded      │     UNTRUSTED Discord message body          │
│  └─────────────────┘                                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Code-Pfad-Analyse

Der Defekt existiert im Discord-Transport-Adapter, wo die Antwortnachricht konstruiert wird. Der erwartete Code-Pfad:

// CORRECT FLOW (Expected)
function buildDiscordMessage(agentResponse) {
    const sanitized = sanitize(s剥离所有内部标记);
    const message = createDiscordEmbed(sanitized);
    return message;
}

// ACTUAL FLOW (Defective)
function buildDiscordMessage(agentResponse) {
    // Sanitization missing or ineffective
    const message = createDiscordEmbed(agentResponse.raw);
    // Raw EXTERNAL_UNTRUSTED_CONTENT markers included
    return message;
}

Anhang-Payload-Korruption

Das “Mülltext”-Muster resultiert aus der Anhang-Text-Extraktion, wobei:

  1. Binäre oder malformed Anhangsdaten verarbeitet werden
  2. Die Extraktion korrupte Unicode/Code-Point-Sequenzen produziert
  3. Diese Sequenzen während der Mehrfach-Anhang-Verarbeitung wiederholt werden
  4. Die korrupte Payload die Inhaltsfilterung umgeht

Subsystem-Verantwortlichkeiten

SubsystemErwartetes VerhaltenTatsächliches Verhalten
DiscordTransportInterne Wrapper vor dem Posten entfernenLeitet rohen Inhalt weiter
ContentSanitizerEXTERNAL_* Marker entfernenFilter deaktiviert oder umgangen
AttachmentHandlerSaubere ExtraktionstexteLeitet korrupte Payload weiter
AsyncCompletionRouterLiefert saubere VervollständigungEnthält Debug-Marker

🛠️ Schritt-für-Schritt-Lösung

Phase 1: Wrapper-Propagation im Discord-Transport deaktivieren

Datei: src/transports/discord/index.ts (oder äquivalentes Transportmodul)

Vorher (Fehlerhaft):

async function handleAssistantMessage(message: ProcessedMessage): Promise<void> {
    const discordMessage = {
        content: message.content,
        embeds: message.embeds
    };
    await this.client.sendMessage(discordMessage);
}

Nachher (Korrigiert):

async function handleAssistantMessage(message: ProcessedMessage): Promise<void> {
    const sanitizedContent = this.sanitizeForDiscord(message.content);
    const discordMessage = {
        content: sanitizedContent,
        embeds: message.embeds
    };
    await this.client.sendMessage(discordMessage);
}

private sanitizeForDiscord(content: string): string {
    // Remove all internal wrapper markers
    const patterns = [
        /<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/gi,
        /<<<END_EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/gi,
        /<<<INTERNAL_[A-Z_]+>>>/gi,
        /Source:\s*(External|Internal)/gi
    ];
    
    let sanitized = content;
    for (const pattern of patterns) {
        sanitized = sanitized.replace(pattern, '');
    }
    
    return sanitized.trim();
}

Phase 2: Anhang-Extraktions-Sanitisierung verstärken

Datei: src/handlers/attachment-extractor.ts

Vorher (Fehlerhaft):

function extractTextFromAttachment(attachment: Attachment): string {
    const raw = processAttachmentBinary(attachment);
    return raw.text || '';
}

Nachher (Korrigiert):

function extractTextFromAttachment(attachment: Attachment): string {
    const raw = processAttachmentBinary(attachment);
    let text = raw.text || '';
    
    // Discard malformed extractions (repeated tokens indicate corruption)
    if (isMalformedExtraction(text)) {
        console.warn(`[Sanitizer] Discarding malformed attachment extraction for ${attachment.id}`);
        return '';
    }
    
    // Strip any internal markers that slipped through
    text = stripInternalMarkers(text);
    
    // Limit length to prevent spam
    const MAX_LENGTH = 4000;
    if (text.length > MAX_LENGTH) {
        text = text.substring(0, MAX_LENGTH) + '\n[Attachment content truncated]';
    }
    
    return text;
}

function isMalformedExtraction(text: string): boolean {
    // Detect repeated token patterns indicating extraction failure
    const tokens = text.toLowerCase().split(/\s+/);
    const uniqueRatio = new Set(tokens).size / tokens.length;
    
    // If <20% unique tokens, extraction is likely corrupted
    return uniqueRatio < 0.2 && tokens.length > 50;
}

Phase 3: Asynchrone Tool-Vervollständigungs-Routing korrigieren

Datei: src/routing/async-completion-router.ts

Vorher (Fehlerhaft):

async function forwardCompletion(result: ToolResult): Promise<void> {
    const message = buildChannelMessage(result);
    await this.transport.post(message);
}

Nachher (Korrigiert):

async function forwardCompletion(result: ToolResult): Promise<void> {
    // Ensure clean payload before routing
    const cleanPayload = this.sanitizer.sanitize(result.payload);
    
    if (cleanPayload.isDirty) {
        console.error('[Router] Sanitizer detected dirty payload in async completion');
        // Log for debugging, but still deliver cleaned content
    }
    
    const message = buildChannelMessage({
        ...result,
        payload: cleanPayload.content
    });
    
    await this.transport.post(message);
}

Phase 4: Transport-Level-Guard hinzufügen

Datei: src/transports/discord/client.ts

Einen finalen Sanitisierungs-Gate vor jedem Discord-API-Aufruf hinzufügen:

async sendMessage(message: DiscordMessage): Promise<API.Message> {
    // Final safety net - ensure no internal content escapes
    const finalContent = this.stripInternalMarkers(message.content);
    
    if (finalContent !== message.content) {
        logger.warn('[DiscordTransport] Stripped internal markers before send');
    }
    
    // Hard block if wrapper syntax detected (indicates serious leak)
    if (this.containsWrapperSyntax(finalContent)) {
        logger.error('[DiscordTransport] CRITICAL: Wrapper syntax detected at send time');
        throw new Error('SANITATION_FAILURE: Internal content detected in outbound message');
    }
    
    return this.api.createMessage(this.channelId, {
        content: finalContent,
        embeds: message.embeds
    });
}

private containsWrapperSyntax(text: string): boolean {
    return /<<<[A-Z_]+>>>/.test(text);
}

🧪 Verifizierung

Testfall 1: Wrapper-Marker-Entfernung

Führen Sie die Sanitisierungsfunktion gegen bekannte interne Inhalte aus:

const { sanitizeForDiscord } = require('./src/transports/discord/sanitizer');

const testCases = [
    {
        input: '<<>>UNTRUSTED Discord message body<<>>',
        expected: 'UNTRUSTED Discord message body'
    },
    {
        input: 'Source: External\nUser message\nSource: Internal',
        expected: 'User message'
    },
    {
        input: '<<>>\nValid response\n<<>>',
        expected: 'Valid response'
    }
];

let passed = 0;
for (const { input, expected } of testCases) {
    const result = sanitizeForDiscord(input);
    if (result === expected) {
        console.log('✅ PASS:', JSON.stringify(result));
        passed++;
    } else {
        console.log('❌ FAIL:', JSON.stringify({ input, expected, got: result }));
    }
}

console.log(`\nResults: ${passed}/${testCases.length} tests passed`);
process.exit(passed === testCases.length ? 0 : 1);

Erwartete Ausgabe:

✅ PASS: "UNTRUSTED Discord message body"
✅ PASS: "User message"
✅ PASS: "Valid response"

Results: 3/3 tests passed

Testfall 2: Ende-zu-Ende Discord-Transport-Test

// Integration test - requires mock Discord client
const { DiscordTransport } = require('./src/transports/discord');

const mockClient = {
    messages: [],
    async sendMessage(msg) {
        this.messages.push(msg);
        return { id: 'test-' + Date.now() };
    }
};

const transport = new DiscordTransport(mockClient);

// Simulate message with internal markers
const dirtyMessage = {
    content: '<<>>Corrupted payload<<>>',
    embeds: []
};

try {
    await transport.handleAssistantMessage(dirtyMessage);
    const sent = mockClient.messages[0];
    
    if (sent.content.includes('<<<')) {
        console.log('❌ FAIL: Wrapper syntax leaked to Discord');
        console.log('Sent content:', sent.content);
        process.exit(1);
    }
    
    console.log('✅ PASS: Message sanitized before Discord send');
    console.log('Final content:', sent.content);
} catch (e) {
    if (e.message.includes('SANITATION_FAILURE')) {
        console.log('✅ PASS: Hard block triggered on dirty content');
    } else {
        throw e;
    }
}

Testfall 3: Malformed Anhang-Erkennung

const { isMalformedExtraction } = require('./src/handlers/attachment-extractor');

// Corrupted payload (high repetition)
const corrupted = Array(200).fill('attach attachment hookup toggle compiler').join(' ');
console.log('Corrupted detection:', isMalformedExtraction(corrupted)); // Should be true

// Valid text
const valid = 'User uploaded a document containing meeting notes from Tuesday.';
console.log('Valid detection:', isMalformedExtraction(valid)); // Should be false

Erwartete Ausgabe:

Corrupted detection: true
Valid detection: false

Verifizierungs-Checkliste

Nach Anwendung der Korrekturen bestätigen:

  • Keine <<<EXTERNAL_UNTRUSTED_CONTENT-Strings in Discord-Nachrichtenverlauf
  • Keine <<<END_EXTERNAL_UNTRUSTED_CONTENT-Strings in Discord-Nachrichtenverlauf
  • Keine Source: External / Source: Internal erscheinen in benutzer sichtbaren Nachrichten
  • Aus Anhängen extrahierte Texte enthalten keine repetitiven Token-Muster (<20% Unique-Ratio)
  • Unit-Tests für sanitizeForDiscord-Funktion bestanden
  • Integrationstests für Discord-Transport bestanden
  • Hard Block löst Fehler aus, wenn Wrapper-Syntax zur Sendezeit erkannt wird

⚠️ Häufige Fehler

Umgebungsspezifische Fallen

Docker-Container-Isolation

Wenn OpenClaw in Docker läuft, stellen Sie sicher, dass das Sanitisierungsmodul korrekt gemountet ist und nicht durch ein Volume überschrieben wird, das auf die fehlerhafte Version zurückfällt:

# Wrong - local source overrides container
docker run -v $(pwd)/src:/app/src openclaw:latest

# Correct - use container's fixed source
docker run openclaw:latest

Windows-Zeilenenden

Der Wrapper-Regex kann fehlschlagen, wenn Inhalte \r\n-Zeilenenden enthalten. Stellen Sie sicher, dass die Sanitisierung beide behandelt:

// BROKEN: Only matches Unix line endings
const pattern = /<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/g;

// FIXED: Handles both Windows and Unix
const pattern = /<<<EXTERNAL_UNTRUSTED_CONTENT[^>\r\n]*>>>/gi;

Node.js-Versionsinkompatibilitäten

Der Set-Konstruktor für die Unique-Ratio-Berechnung erfordert Node.js 12+. Überprüfen Sie die Kompatibilität:

// Feature detection fallback
const uniqueRatio = typeof Set !== 'undefined' 
    ? new Set(tokens).size / tokens.length 
    : [...new Set(tokens)].length / tokens.length;

Konfigurations-Fallen

Sanitisierung durch Umgebungsvariable deaktiviert

Einige Deployments deaktivieren Sanitisierung zum Debuggen, was dieses Leck verursacht:

# .env file - ensure sanitization is NOT disabled
SANITIZATION_ENABLED=true
# SANITIZATION_ENABLED=false  ← REMOVE OR SET TO TRUE

Transport-Konfiguration erbt keinen Base-Sanitizer

Wenn Sie eine benutzerdefinierte Discord-Transport-Implementierung verwenden, stellen Sie sicher, dass sie den Base-ContentSanitizer erbt:

// WRONG: Custom transport bypasses sanitization
class DiscordTransportCustom {
    async send(msg) { /* direct send without sanitization */ }
}

// CORRECT: Inherit sanitization
class DiscordTransportCustom extends BaseTransport {
    async send(msg) {
        return super.send(this.sanitizer.sanitize(msg));
    }
}

Runtime-Edge-Cases

Unicode-Normalisierungsangriffe

Bösartige Inhalte können Unicode-Lookalike-Zeichen verwenden, um Mustervergleiche zu umgehen:

// Attempted bypass: Cyrillic 'а' instead of Latin 'a'
const malicious = '<<<ЕXTERNAL_UNTRUSTED_CONTENT id="1">>>'; // Different chars

// Defensive: Normalize before pattern matching
const normalized = content.normalize('NFKC');
const sanitized = stripInternalMarkers(normalized);

Race Condition bei gleichzeitiger Nachrichten-Sanitisierung

Wenn mehrere asynchrone Tool-Vervollständigungen gleichzeitig ausgelöst werden:

// Ensure thread-safe sanitization by not mutating shared state
// WRONG: Mutates input in place
function sanitize(content) {
    content = content.replace(pattern1, '');
    return content.replace(pattern2, ''); // Returns mutated original
}

// CORRECT: Immutable operations
function sanitize(content) {
    return content
        .replace(pattern1, '')
        .replace(pattern2, '');
}

Leeres Sanitisierungsergebnis

Wenn Sanitisierung alle Inhalte entfernt, stellen Sie sicher, dass die Nachricht nicht gesendet wird (vermeidet leeren Spam):

const sanitized = stripInternalMarkers(raw);
if (!sanitized.trim()) {
    logger.warn('[Discord] Sanitization produced empty message, discarding');
    return; // Do not post to Discord
}

🔗 Zugehörige Fehler

Direkt zugehörige Probleme

Fehler/ProblemBeschreibungVerbindung
EXTERNAL_UNTRUSTED_CONTENT Wrapper-LeckRohe interne Marker für Benutzer sichtbarPrimäres Problem - identisches Symptom
Anhang-Text-ExtraktionskorruptionMüll-/malformed Text von AnhängenGleiche Ursache: fehlende Sanitisierungsgrenze
Asynchroner Tool-Vervollständigungs-SpamDuplizierte/kaputte Vervollständigungen in KanälenTeilt Transport-Rendering-Defekt
Discord-Rate-Limit-FehlerKann auftreten, wenn Leck Nachrichten-Spam-Schleife verursachtSekundäres Symptom von Müllinhalten
Nachrichtenwarteschlangen-BackupWenn Transport wiederholt an schmutzigem Inhalt scheitertNachgelagerte Konsequenz von unsanitisiertem Input

Historisch zugehörige Probleme

Issue IDTitelRelevanz
GH-XXXSanitizer not applied to async completion payloadsDirekter Vorgänger - Korrektur nicht auf alle Pfade propagiert
GH-YYYDiscord transport bypasses content filtering in dev modeUmgebungsspezifische Variante des Grenzfehlers
GH-ZZZAttachment extraction returning binary garbageGleicher Korruptionsmechanismus, anderes Subsystem
GH-AAAInternal wrapper syntax appearing in logsDeutet auf Wrapper-Proliferation im gesamten Codebase hin

Fehlercode-Referenz

CodeBedeutungKorrektur-Relevanz
DISCORD_TRANSPORT_001Nachricht überschreitet 2000-Zeichen-LimitSanitisierung sollte kürzen, nicht fehlschlagen
DISCORD_TRANSPORT_002Sanitisierungsfehler bei ausgehender NachrichtHard Block deutet auf ernsthaftes Leck hin
CONTENT_SANITIZE_001Mustervergleich bei Input fehlgeschlagenRegex-Schwachstelle ermöglicht Umgehung
ATTACHMENT_EXTRACT_001Binäre Extraktion produzierte Nicht-TextKorrupte Payload verwerfen, nicht weiterleiten
ASYNC_COMPLETION_001Schmutzige Payload in Warteschlange erkanntPre-Delivery-Sanitisierung fehlt

Zugehörige Konfigurationsparameter

ParameterOrtStandardSicherheitsauswirkung
SANITIZATION_ENABLEDEnvironmenttrueWenn false, wird alle Sanitisierung umgangen
DISCORD_STRICT_MODEConfigfalseWenn true, wird Hard Block bei Wrapper-Erkennung aktiviert
ATTACHMENT_MAX_EXTRACT_CHARSConfig4000Verhindert Spam aus überdimensionierten Extraktionen
ASYNC_COMPLETION_SANITIZEConfigtrueMuss für async-Pfad aktiviert bleiben

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.