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
- Envelope markers rendered as visible content β The delimiter syntax appears verbatim in the agent prompt input, breaking the intended sanitization boundary.
- 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.
- 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:
- Web UI preprocessor β `control-ui/assets/index-*.js` contains regex strip patterns matching the envelope syntax
- 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.
π Related Errors
Related GitHub Issues
- #62939 β Prompt injection defense at tool result and message boundaries
- #71992 β control UI webchat duplicates every assistant reply
Related Error Codes and Symptoms
| Error/Symptom | Description | Connection |
|---|---|---|
ENVELOPE_MARKER_LEAK | Raw markers visible in prompt context | Primary symptom of this bug |
MESSAGE_DUPLICATION | Same message frame rendered twice | Secondary symptom, shared pipeline failure |
PROMPT_INJECTION_AMBIGUITY | Boundary confusion between instruction and content | Architectural consequence of envelope leak |
CHANNEL_METADATA_LEAK | Channel topic/name visible as untrusted content | Specific manifestation on Discord channel #sprites-of-thornfield |
STRIP_LOGIC_REGRESSION | Existing sanitization bypassed | Root cause class for this bug |
Related Code Locations
| File | Role | Status |
|---|---|---|
control-ui/assets/index-*.js | Web UI strip-logic | Suspected regression point |
apps/shared/OpenClawChatUI/ChatMarkdownPreprocessor.swift | Swift UI strip-logic | To verify integrity |
gateway/services/discord-handler.ts | Discord message pipeline | Suspected bypass path |
gateway/services/message-store.ts | Message persistence | May contain stale unstripped content |
Related Architectural Components
- 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