May 07, 2026

Bug: Raw <<<EXTERNAL_UNTRUSTED_CONTENT>>> envelope-markers visible in agent-input on Discord channel sessions (regression against existing strip-logic)

Raw envelope-marker syntax is surfacing in agent-visible prompts on Discord channel sessions, bypassing existing strip-logic in control-ui and Swift UI preprocessors.

πŸ” Symptoms

Visual Manifestation

When processing inbound Discord channel messages, the raw envelope-marker syntax renders as visible text within agent-visible prompt context instead of being stripped before render.

Example of corrupted agent-input:


<<>>
Source: Channel metadata
---
UNTRUSTED channel metadata (discord)
Discord channel topic:
prince's frond-house, maybe dorm room, sometimes kitchen, sometimes commune.
<<Observed Failure Patterns
  1. Envelope markers rendered as visible content β€” The delimiter syntax appears verbatim in the agent prompt input, breaking the intended sanitization boundary.
  2. Message body duplication β€” The same inbound message frequently appears twice within a single chat-frame, with duplicates containing their own metadata blocks and inline envelope-marker content.
  3. Cross-seat contamination β€” The regression manifests identically across multiple prince-agent-seats (cael, silas, elliott) accessing the same Discord channel session.

Affected Context

Parameter Value
Channel #sprites-of-thornfield
Channel ID 1466192485440164011
Affected Seats cael, silas, elliott
Sender Types Human users, prince agents, code-agent webhook bots, system events
Session Duration Multi-hour (reproduces consistently)

Sample Corrupted Message IDs

The following message IDs from the reported session demonstrate the bug in agent-input context:

  • 1503083125876461609 (Elliott)
  • 1503082943936204882 (Silas)
  • 1503082349833883748 (Elliott)
  • 1503084521405415610 (Elliott β€” meta-demonstration: message describing the bug contains the bug in its inbound rendering)

🧠 Root Cause

Architectural Context

The envelope-marker system (<<<EXTERNAL_UNTRUSTED_CONTENT>>> / <<<END_EXTERNAL_UNTRUSTED_CONTENT>>>) exists to establish a boundary between trusted instruction and untrusted user-supplied content. Strip-logic was implemented in two render paths:

  1. Web UI preprocessor β€” `control-ui/assets/index-*.js` contains regex strip patterns matching the envelope syntax
  2. Swift UI preprocessor β€” `apps/shared/OpenClawChatUI/ChatMarkdownPreprocessor.swift` contains strip logic for the same envelope shape

Failure Sequence Analysis

Three plausible failure-classes exist, ranked by likelihood based on the observed multi-seat consistency:

1. Strip-Logic Regression (Highest Probability)

The regex patterns in the existing strip-logic may have been inadvertently modified or bypassed:

// Expected pattern structure in control-ui/assets/index-*.js
/\<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>[\s\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/g

// Possible regression scenarios:
// - Pattern modified to non-greedy or wrong character class
// - Pattern removed entirely during refactor
// - Pattern applied at wrong pipeline stage

2. Render Path Bypass (Moderate Probability)

The Discord channel ingest path may route messages through a render pipeline that does not pass through either preprocessor:

// Message flow for standard channels:
// Discord API β†’ Gateway β†’ Message Store β†’ ChatMarkdownPreprocessor.swift β†’ Agent Context
//                                                      ↓
//                                              STRIP-LOGIC ACTIVE

// Possible Discord-specific bypass:
// Discord API β†’ Gateway β†’ Discord Handler β†’ Direct Agent Context (NO PREPROCESSOR)
//                                                      ↓
//                                              STRIP-LOGIC BYPASSED

The Discord integration may have a distinct message handler that writes directly to agent context without routing through the standard ChatMarkdownPreprocessor or the web UI preprocessor.

3. Stale Bundle/State (Low Probability)

If the Discord handler or channel service was deployed independently from the UI bundles, the running process may be executing old code that lacks the strip-logic entirely:

// Timeline potential:
// T1: Strip-logic added to source code
// T2: UI bundles rebuilt with strip-logic (correct)
// T3: Discord handler service deployed without strip-logic (stale)
// T4: Agent-input receives unstripped content

Prompt-Injection Boundary Compromise

The envelope-marker strip-logic exists specifically to prevent ambiguity at the instruction/content boundary. When markers render as visible text, the agent cannot reliably distinguish between:

  • System instruction about how to handle external content
  • User content that happens to contain marker-like syntax

This regression compromises the defense-in-depth that the envelope system provides.

πŸ› οΈ Step-by-Step Fix

Phase 1: Diagnostic Verification

Before applying fixes, confirm the current state of strip-logic across affected code paths:

# Check control-ui for envelope strip patterns
grep -n "EXTERNAL_UNTRUSTED_CONTENT" control-ui/assets/*.js | head -20

# Check Swift preprocessor for envelope strip logic
grep -n "EXTERNAL_UNTRUSTED_CONTENT" apps/shared/OpenClawChatUI/ChatMarkdownPreprocessor.swift

# Identify Discord-specific message handlers
find . -type f -name "*.ts" -o -name "*.js" | xargs grep -l "Discord\|discord" | head -10

Phase 2: Strip-Logic Audit

2.1: Verify Web UI Strip-Logic Integrity

Locate and inspect the regex pattern in control-ui/assets/index-*.js:

// Expected pattern (verify this exists and is correct):
const envelopePattern = /<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>[\s\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/g;

// If missing or corrupted, add or restore:
// function stripExternalContent(input) {
//   return input.replace(envelopePattern, '');
// }

// Apply to message content before render:
// processedContent = stripExternalContent(rawContent);

2.2: Verify Swift Preprocessor Strip-Logic

Inspect apps/shared/OpenClawChatUI/ChatMarkdownPreprocessor.swift:

// Expected Swift implementation:
static func stripExternalContentMarkers(from input: String) -> String {
    let pattern = "<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>[\\s\\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>"
    guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
        return input
    }
    let range = NSRange(input.startIndex..., in: input)
    return regex.stringByReplacingMatches(in: input, options: [], range: range, withTemplate: "")
}

// Apply in preprocessing pipeline before message delivery

Phase 3: Discord Handler Pipeline Fix

3.1: Identify Discord Message Handler

Locate the Discord-specific message processing code and verify whether it routes through preprocessors:

# Find Discord handler files
find . -path "*/discord/*" -name "*.ts" -o -path "*/channels/*" -name "*.ts" | xargs ls -la

# Search for Discord message processing
grep -rn "discord\|Discord" --include="*.ts" | grep -i "message\|handler\|processor" | head -15

3.2: Add Strip-Logic to Discord Pipeline

If the Discord handler bypasses preprocessors, add envelope stripping at the point of message receipt:

// Example fix for Discord message handler (pseudo-code):
async function handleDiscordMessage(rawMessage: DiscordMessage): Promise<AgentContext> {
    // 1. Parse message
    const parsed = parseDiscordMessage(rawMessage);
    
    // 2. APPLY STRIP-LOGIC HERE (if not covered by downstream preprocessors)
    const envelopePattern = /<<<EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>[\s\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT[^>]*>>>/g;
    parsed.content = parsed.content.replace(envelopePattern, '');
    
    // 3. Route to agent context
    return buildAgentContext(parsed);
}

Phase 4: Deployment

# Rebuild affected services
cd control-ui && npm run build
cd apps/shared && swift build  # or appropriate Swift build command

# Restart Discord handler service
openclaw-cli gateway restart --service discord-handler

# Clear any cached message state
openclaw-cli gateway restart --clear-cache

πŸ§ͺ Verification

Step 1: Controlled Envelope-Injection Test

Send a test message through the Discord channel with known envelope-marker content:

# Test message to send via Discord (channel #sprites-of-thornfield):
# Content includes envelope markers with unique trace ID

<<>>
This is test content that should be stripped.
The markers above and below should not appear in agent input.
<<>>

Expected outcome: Agent receives message body only; no envelope markers visible in agent-input context.

Step 2: Agent Context Inspection

Query the agent’s input context after the test message:

# Via OpenClaw CLI (if available)
openclaw-cli agent inspect-input --seat=cael --message-id=<latest>

# Or via debug endpoint
curl -X POST https://gateway.openclaw.internal/debug/message-context \
  -H "Authorization: Bearer $OPENCLAW_TOKEN" \
  -d '{"message_id": "<test_message_id>", "seat": "cael"}'

Expected output:

{
  "message_id": "<test_message_id>",
  "content": "This is test content that should be stripped.\nThe markers above and below should not appear in agent input.",
  "has_envelope_markers": false,
  "strip_verified": true
}

Step 3: Cross-Seat Verification

Verify the fix propagates across all affected seats:

# Test across all three prince seats
for seat in cael silas elliott; do
  echo "Testing seat: $seat"
  openclaw-cli agent inspect-input --seat=$seat --message-id=<test_message_id> \
    | grep -i "envelope\|untrusted" && echo "FAIL: markers found" || echo "PASS"
done

Expected output: All three seats report PASS (no envelope markers found).

Step 4: Regression Suite Verification

# Run existing envelope-strip tests (if available)
npm test -- --grep "envelope"
# or
swift test --filter "Envelope"

# Expected: All strip-logic tests pass
# Test Suite: EnvelopeStripping
#   βœ“ stripExternalContentMarkers_basic
#   βœ“ stripExternalContentMarkers_nested
#   βœ“ stripExternalContentMarkers_multiple
#   βœ“ stripExternalContentMarkers_nonePresent

Step 5: Message Duplication Verification

Monitor for the duplicate-frame symptom during a live session:

# Monitor Discord channel for duplicate messages
openclaw-cli gateway logs --service=discord-handler --follow | grep -i "duplicate\|DUP"

# After fix, no DUP markers should appear

Expected: No duplicate message frames observed in the channel session.

⚠️ Common Pitfalls

1. Incomplete Strip-Logic Application

Pitfall: Adding strip-logic to only one render path (web UI OR Swift UI) but not both, leading to inconsistent behavior across client types.

Mitigation: Verify strip-logic exists and is functional in both:

  • Web client render path (`control-ui/assets/index-*.js`)
  • Swift client render path (`ChatMarkdownPreprocessor.swift`)
  • Discord handler pipeline (if distinct from above)

2. Regex Edge Cases

Pitfall: The envelope pattern may fail on malformed or nested content.

// Problematic cases:
// - Newlines within marker syntax
// - Nested untrusted content blocks
// - Malformed IDs (quotes, special chars)

<<>>  // May not match

<<>>  // May not match

Mitigation: Use a robust regex with character class exclusions and test with pathological inputs.

3. Pipeline Ordering Dependencies

Pitfall: Strip-logic applied before markdown rendering may be re-introduced by subsequent processing steps.

Mitigation: Verify the strip operation occurs after all transformations:

// Correct pipeline order:
// 1. Parse raw message
// 2. Apply channel-specific transformations
// 3. STRIP envelope markers (after transformations)
// 4. Render to UI / agent context

4. Stale Cache State

Pitfall: Message content cached in the message store may retain unstripped versions from before the fix.

Mitigation: After deploying the fix, force a cache refresh:

openclaw-cli gateway restart --clear-cache --service=message-store

5. macOS Development Environment Differences

Pitfall: Strip-logic tested locally on macOS may behave differently than in Docker/Linux production containers due to regex engine variations.

Mitigation: Always verify strip-logic in a Docker-equivalent environment before deployment.

6. Case-Sensitivity Issues

Pitfall: Discord message content may arrive with inconsistent casing (<<<external_untrusted_content>>>) that the strip pattern doesn’t match.

Mitigation: Apply case-insensitive flag to regex or normalize casing before stripping.

  • #62939 β€” Prompt injection defense at tool result and message boundaries
    Related family: envelope system design, but addressed as feature-request rather than regression. This issue demonstrates why the defense is critical.
  • #71992 β€” control UI webchat duplicates every assistant reply
    Related family on the duplication shape, but on the outbound (assistant→user) side. This report is the inbound (user→agent) mirror of the same underlying pipeline issue.
Error/SymptomDescriptionConnection
ENVELOPE_MARKER_LEAKRaw markers visible in prompt contextPrimary symptom of this bug
MESSAGE_DUPLICATIONSame message frame rendered twiceSecondary symptom, shared pipeline failure
PROMPT_INJECTION_AMBIGUITYBoundary confusion between instruction and contentArchitectural consequence of envelope leak
CHANNEL_METADATA_LEAKChannel topic/name visible as untrusted contentSpecific manifestation on Discord channel #sprites-of-thornfield
STRIP_LOGIC_REGRESSIONExisting sanitization bypassedRoot cause class for this bug
FileRoleStatus
control-ui/assets/index-*.jsWeb UI strip-logicSuspected regression point
apps/shared/OpenClawChatUI/ChatMarkdownPreprocessor.swiftSwift UI strip-logicTo verify integrity
gateway/services/discord-handler.tsDiscord message pipelineSuspected bypass path
gateway/services/message-store.tsMessage persistenceMay contain stale unstripped content
  • Envelope-Marker System β€” The security boundary mechanism that this bug defeats
  • Message Preprocessor Pipeline β€” Shared processing logic that should apply strip-logic
  • Channel Metadata Injection β€” The source of the leaked content (`Source: Channel metadata` block)
  • Agent Context Assembly β€” The final render point where markers are visible to agents

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.