Skip to content

fix(session): prevent stale threadId leaking into non-thread sessions#18528

Merged
steipete merged 1 commit intoopenclaw:mainfrom
j2h4u:fix/stale-thread-id-fallback
Feb 16, 2026
Merged

fix(session): prevent stale threadId leaking into non-thread sessions#18528
steipete merged 1 commit intoopenclaw:mainfrom
j2h4u:fix/stale-thread-id-fallback

Conversation

@j2h4u
Copy link
Contributor

@j2h4u j2h4u commented Feb 16, 2026

Summary

  • Fix stale lastThreadId fallback causing bot to reply in wrong Telegram DM topic
  • Add tests for thread ID persistence behavior

Problem/Context

When a user interacts with the bot inside a DM topic/thread, the session persists lastThreadId. If the user later sends a plain DM message (no topic), ctx.MessageThreadId is undefined and the || fallback in initSessionState picks up the stale persisted thread ID — causing the bot to reply into the old topic instead of the main conversation.

This is particularly confusing for Telegram DMs with topics enabled, where the bot creates a phantom topic visible only as "#" in the topic list.

Solution

Only fall back to baseEntry.lastThreadId for thread sessions (where isThread is true). Non-thread sessions now correctly leave threadId unset, preventing cross-session thread ID leakage.

- const lastThreadIdRaw = ctx.MessageThreadId || baseEntry?.lastThreadId;
+ const lastThreadIdRaw = ctx.MessageThreadId || (isThread ? baseEntry?.lastThreadId : undefined);

Test Plan

  • Added test: non-thread session does not inherit stale threadId from a previous thread interaction
  • Added test: thread session correctly preserves threadId across messages
  • All 37 existing session tests pass

Sign-Off

  • Models used: Claude Opus 4.6
  • Submitter effort summary: Discovered while debugging bot replying in invisible Telegram DM topic. Root-caused to stale lastThreadId fallback in session state initialization. Verified fix with new tests.
  • Agent notes: The isThread flag was already computed earlier in initSessionState — reusing it for the guard keeps the change minimal.

—Calculon, Actor Extraordinaire (feat. Opus 4.6)

Greptile Summary

Adds defensive guard to prevent non-thread sessions from inheriting stale lastThreadId from previous thread interactions. The fix correctly scopes the baseEntry?.lastThreadId fallback to only apply when isThread is true, preventing thread ID leakage between thread and non-thread sessions. Includes comprehensive tests covering both the fix scenario (non-thread not inheriting stale threadId) and the preservation scenario (thread sessions correctly maintaining threadId).

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The change is a focused, defensive fix with clear logic: only allowing threadId fallback when actually in a thread session. The implementation is minimal (single line change + comment), the fix prevents a real bug (phantom thread replies), comprehensive tests verify both the fix and preservation behavior, and all 37 existing session tests pass.
  • No files require special attention

Last reviewed commit: 4ba2da4

When a user interacts with the bot inside a DM topic (thread), the
session persists `lastThreadId`. If the user later sends a message
from the main DM (no topic), `ctx.MessageThreadId` is undefined and
the `||` fallback picks up the stale persisted value — causing the
bot to reply into the old topic instead of the main conversation.

Only fall back to `baseEntry.lastThreadId` for thread sessions where
the fallback is meaningful (e.g. consecutive messages in the same
thread). Non-thread sessions now correctly leave threadId unset.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@steipete steipete merged commit 5f821ed into openclaw:main Feb 16, 2026
25 checks passed
@j2h4u j2h4u deleted the fix/stale-thread-id-fallback branch February 17, 2026 00:12
chilu18 added a commit to chilu18/openclaw that referenced this pull request Feb 19, 2026
Addresses openclaw#18528: Thread ID leakage in error messages and logs

Comprehensive security documentation for preventing thread/session ID
leakage through error messages, API responses, and logs.

Security impacts documented:
1. Conversation enumeration - sequential scanning of leaked thread IDs
   Attack: Use exposed thread_12345 to try thread_12346, 12347, etc.
   Impact: Access to conversations not owned by attacker
2. Cross-session contamination - injecting leaked thread ID in API requests
   Attack: Send messages to wrong conversation via leaked ID
   Impact: Data leakage, privacy violation
3. Replay attacks - reusing leaked session IDs
   Attack: Resume expired sessions with leaked thread ID
   Impact: Unauthorized session access

Common leakage vectors:
- Error messages exposing internal thread IDs to users
- API responses including internal identifiers
- Stack traces with thread ID parameters
- Webhook payloads to external services
- Client-side console logs in production

Detection methods provided:
- Scan error messages for ID patterns (thread_, session_, conv_)
- Inspect API responses for internal identifiers
- Monitor webhook payloads for ID exposure
- Review client-side code for console.log leaks

Remediation steps:
1. Audit error handling - find all error throws with IDs
2. Implement error sanitizer - regex replace internal IDs
3. Use opaque identifiers - SHA256 hash for external use
4. Configure production logging - disable debug, sanitize errors
5. Validate external integrations - check webhook payloads

Testing scenarios:
- Invalid thread ID → generic "not found" error
- Unauthorized access → "access denied" not "belongs to user_X"
- Stack traces → no thread IDs in traces

Code examples provided:
- Error sanitizer utility (regex replacements)
- Opaque identifier generation (SHA256 hash)
- Reverse lookup with mapping table
- Response sanitization middleware
- Log pattern detection script

Best practices:
- Principle of least information (expose only what's needed)
- Separate internal/external IDs
- Sanitize all user-facing output by default
- Log detailed info internally, show generic errors publicly
- Regular security audits (monthly/quarterly)

Configuration recommendations:
- sanitizeErrors: true
- includeInternalIds: false
- stackTracesInProduction: false
- errorFormat: "generic"

Related: openclaw#20912 (API key exposure), openclaw#20914 (plugin fail-open)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants