Skip to content

fix(msteams): handle invalid JSON escape sequences in Bot Framework activities#34581

Closed
hddevteam wants to merge 5 commits intoopenclaw:mainfrom
hddevteam:fix/msteams-json-escape-sequences
Closed

fix(msteams): handle invalid JSON escape sequences in Bot Framework activities#34581
hddevteam wants to merge 5 commits intoopenclaw:mainfrom
hddevteam:fix/msteams-json-escape-sequences

Conversation

@hddevteam
Copy link
Copy Markdown

Problem

Some Bot Framework clients (e.g. certain MS Teams desktop/mobile clients) send
activity payloads that contain invalid JSON escape sequences — bare backslashes
followed by characters not defined in RFC 8259
(e.g. \p, \q, \c).

The existing express.json() middleware uses body-parser's strict JSON parser
which throws:

SyntaxError: Bad escaped character in JSON at position 388

This causes the webhook handler to return a non-200 response to Azure Bot
Service. Azure Bot Service interprets non-200 responses as delivery failures and
enters exponential backoff — dropping all subsequent user messages until the
backoff window expires (which can be minutes to hours).

Observed Symptom

  • First message (e.g. conversationUpdate on channel create) triggers the
    SyntaxError, msteams provider replies with 4xx/5xx
  • Azure Bot Service backoff begins
  • All subsequent user messages are silently dropped by Azure (not delivered to
    the local endpoint)
  • Users send messages in Teams but the bot never responds

Fix

Replace express.json() with a two-pass raw body parsing approach:

  1. Accept body as raw Buffer via express.raw({ type: 'application/json' })
  2. First pass: try standard JSON.parse() on the raw body
  3. Second pass (if first fails with SyntaxError): attempt to repair the
    payload by double-escaping bare backslashes via regex:
    /\\([^"\\/bfnrtu])/g → \\\\$1
  4. If the repaired payload parses successfully → log a warning and continue
  5. If the payload still cannot be parsed → respond HTTP 200 to prevent
    Azure Bot Service backoff, and log the error for investigation

Testing

Observed and confirmed with a real Teams bot deployment:

  1. Azure Bot Service delivered a conversationUpdate activity with an invalid
    escape sequence at position 388 → previously caused SyntaxError + Azure
    backoff for subsequent messages
  2. With this fix, activities with minor JSON escaping issues are repaired and
    processed normally
  3. Completely unrecoverable payloads are acknowledged with 200 to prevent
    backoff loops

Notes

  • express.raw() is already part of Express's built-in middleware (same as
    express.json()), no new dependencies needed
  • The regex fix targets only actual bare backslashes before non-escape chars;
    valid JSON escape sequences (\", \\, \/, \b, \f, \n, \r, \t,
    \uXXXX) are preserved
  • This is a conservative fix that preserves all existing behaviour for
    well-formed payloads

Copilot AI review requested due to automatic review settings March 4, 2026 15:09
@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: XS labels Mar 4, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b2a2fb988

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/msteams/src/monitor.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 4, 2026

Greptile Summary

This PR replaces express.json() with a two-pass raw-body parsing strategy (express.raw() + a custom JSON-repair middleware) to gracefully handle Bot Framework activity payloads that contain invalid JSON escape sequences (e.g. bare \p, \q). The fix correctly prevents Azure Bot Service exponential backoff by ensuring the endpoint always returns HTTP 200, even for unrecoverable payloads.

Key changes:

  • express.raw({ type: "application/json" }) is used instead of express.json() so the raw bytes are available for repair
  • A custom middleware attempts JSON.parse first, then falls back to a regex-based bare-backslash repair pass before re-parsing
  • The error handler is extended to catch SyntaxError from irreparable payloads and respond with HTTP 200
  • A TypeScript type assertion (err as { status: number }).status is added to satisfy strict typing on the existing 413 check

Potential issue:
The repair regex /\\([^"\\/bfnrtu])/g does not account for already-escaped backslashes (\\). In a payload that mixes a valid \\x sequence with a separate invalid escape, the regex can misread the second \ of a \\ pair and corrupt the originally valid segment. In most cases this still results in a failed parse (HTTP 200 fallback), but for specific inputs (odd numbers of backslashes before an invalid escape character) the repaired payload can parse successfully with a silently wrong field value.

Confidence Score: 4/5

  • This PR is safe to merge with minor caveats — the fix correctly addresses the Azure Bot Service backoff problem and degrades gracefully for edge cases.
  • The middleware structure, error-handler ordering, and content-type handling are all correct. The two-pass approach is sound and matches the described behavior for well-formed, lightly-malformed, and unrecoverable payloads. The only real concern is the repair regex edge case with consecutive backslashes, which in most real-world scenarios still falls back to HTTP 200 gracefully. The TypeScript type-cast fix is also a strictly correct improvement.
  • Line 289 of extensions/msteams/src/monitor.ts — the repair regex should be reviewed for the consecutive-backslash edge case.

Last reviewed commit: 5b2a2fb

Comment thread extensions/msteams/src/monitor.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the MS Teams provider’s Express webhook handling to tolerate Bot Framework activity payloads containing invalid JSON escape sequences (e.g., \p) so the endpoint avoids returning non-200 responses that trigger Azure Bot Service exponential backoff.

Changes:

  • Replace express.json() with express.raw() and a custom two-pass JSON parse/repair step.
  • Add error handling to acknowledge unrecoverable JSON parse errors with HTTP 200 while logging details.
  • Tighten the 413 (“Payload too large”) error guard type check.

Comment thread extensions/msteams/src/monitor.ts
Comment thread extensions/msteams/src/monitor.ts Outdated
Comment thread extensions/msteams/src/monitor.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88468fab95

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/msteams/src/monitor.ts Outdated
@hddevteam
Copy link
Copy Markdown
Author

Thanks for the thorough reviews! All three issues have been addressed in the latest commits:

Regex edge case (Greptile + chatgpt-codex-connector feedback)
The original /\\([^"\\/bfnrtu])/g could corrupt valid \\q sequences. Fixed by extracting repairJsonEscapes() as a named function using the alternation pattern /(\\\\)+|\\([^"\\/bfnrtu])/g — valid \\ pairs are consumed first by the left branch and left unchanged; only the right branch (lone bare escapes) triggers the repair.

A dedicated regression test (monitor-json-repair.test.ts) now covers the mixed-validity edge case identified by Greptile: a payload with both a valid \\q and an invalid \P in different fields.

express.raw missing from lifecycle test mock (copilot-pull-request-reviewer feedback)
Added raw: makeNoopMiddleware alongside json in the vi.mock("express", ...) factory in monitor.lifecycle.test.ts, preventing the express.raw is not a function startup crash.

All changes are in commit ef2f5fb.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef2f5fbdeb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/msteams/src/monitor.ts Outdated
@hddevteam
Copy link
Copy Markdown
Author

Fixed CI formatting failure in commit 37b4a2c.

The Check types and lint and oxfmt step was failing because monitor.ts and monitor-json-repair.test.ts were not formatted with oxfmt. Ran oxfmt --write on both files locally and pushed the result (2 files changed, 7 insertions).

@BradGroux BradGroux self-assigned this Mar 10, 2026
@BradGroux
Copy link
Copy Markdown
Member

Hi @hddevteam — thanks for the submission. I’m the new Microsoft Teams maintainer for OpenClaw. Please give me a day or two to work through the open Teams backlog. Also, join the Twitter community for daily MS Teams feedback + updates: https://x.com/i/communities/2031170403607974228

1 similar comment
@BradGroux
Copy link
Copy Markdown
Member

Hi @hddevteam — thanks for the submission. I’m the new Microsoft Teams maintainer for OpenClaw. Please give me a day or two to work through the open Teams backlog. Also, join the Twitter community for daily MS Teams feedback + updates: https://x.com/i/communities/2031170403607974228

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 26, 2026

Codex review: needs maintainer review before merge.

What this changes:

The PR changes the MS Teams webhook middleware from express.json() to raw body parsing with a JSON escape repair pass, a 200 acknowledgement for unrecoverable parse failures, and lifecycle/repair regression tests.

Maintainer follow-up before merge:

This is an open implementation PR for a valid MS Teams webhook bug, and the remaining action is maintainer review or a small test-quality request rather than an automated replacement fix.

Security review:

Security review cleared: The diff keeps JWT validation before body parsing, preserves the body-size limit, adds no dependency or workflow changes, and does not expose a concrete supply-chain issue.

Review details

Best possible solution:

Review and land this MS Teams-owned webhook parsing fix, ideally with a small production-middleware regression test or an exported local helper test so the repair logic is covered directly. No core/plugin API change appears necessary.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main and v2026.4.27 put authenticated application/json Bot Framework requests through express.json(), and a body string containing a bare escape such as \p is enough for JSON.parse to throw before the adapter runs.

Is this the best way to solve the issue?

Yes, broadly. Parsing raw bytes after JWT validation preserves the existing pre-parse auth hardening and gives the plugin a narrow place to repair Teams payloads; the safer pre-merge refinement is stronger production-path test coverage, not a different architecture.

Acceptance criteria:

  • pnpm test extensions/msteams/src/monitor.lifecycle.test.ts extensions/msteams/src/tests/monitor-json-repair.test.ts
  • pnpm check:changed in Testbox before merge

What I checked:

  • current-main behavior: Current main still parses the authenticated MS Teams webhook body with express.json() and only handles payload-too-large errors, so malformed JSON escape sequences are still handled by the strict body parser rather than repaired. (extensions/msteams/src/monitor.ts:308, 554b32feeac0)
  • latest release behavior: The latest release tag v2026.4.27 has the same express.json() parser and no express.raw, repairJsonEscapes, or SyntaxError 200-ack path in the Teams monitor. (extensions/msteams/src/monitor.ts:308, cbc2ba093146)
  • proposed middleware change: The PR diff replaces strict JSON middleware with express.raw({ type: "application/json", limit: ... }), parses the Buffer manually, repairs bare backslash escapes on the fallback pass, and returns HTTP 200 for unrecoverable SyntaxError payloads. (extensions/msteams/src/monitor.ts:346, 7155816f129e)
  • review feedback addressed: Earlier review comments flagged the simple regex corrupting escaped backslash pairs and the missing express.raw lifecycle-test mock; the latest diff uses a run-length repair helper and adds raw to the express mock. (extensions/msteams/src/monitor.lifecycle.test.ts:49, 7155816f129e)
  • dependency behavior checked: Express 5.2.1 exports raw and json from body-parser; body-parser raw reads the request into a Buffer, while body-parser json calls JSON.parse and normalizes SyntaxError parse failures. That supports the PR's raw-body approach.
  • CI/check status: The current PR head has successful build, lint, type, extension, security, and fast check runs on April 28, 2026; later stale-bot runs are not validation runs. (7155816f129e)

Likely related people:

  • SidU: Auth-before-body-parse and the current Teams SDK webhook surface appear in the merged Teams AI agent UX migration, which is the central webhook pipeline this PR modifies. (role: introduced behavior; confidence: high; commits: cd90130877f1; files: extensions/msteams/src/monitor.ts, extensions/msteams/src/sdk.ts)
  • BradGroux: He self-identified in the PR discussion as the Microsoft Teams maintainer, is assigned in the current PR metadata, and has recent merged MS Teams error-handling work in this area. (role: likely follow-up owner; confidence: high; commits: 03c64df39fe7; files: extensions/msteams/src/monitor.ts, extensions/msteams/src/sdk.ts)
  • steipete: Recent main history and GitHub path history show repeated MS Teams monitor, lifecycle, SDK, and release maintenance around the affected files. (role: recent maintainer; confidence: high; commits: 866bd91c659c, 270630ba35e4, b3bc60ae259b; files: extensions/msteams/src/monitor.ts, extensions/msteams/src/monitor.lifecycle.test.ts, extensions/msteams/src/sdk.ts)
  • jacobtomlinson: Auth-before-JSON parsing in the Teams webhook lifecycle was previously changed in a merged PR, making this person relevant to the ordering contract the PR preserves. (role: adjacent owner; confidence: medium; commits: 3834d47099dd; files: extensions/msteams/src/monitor.lifecycle.test.ts, extensions/msteams/src/monitor.ts)

Remaining risk / open question:

  • I did not replay a live Teams/Azure Bot Service delivery because this was a read-only repository review.
  • The added repair test mirrors the helper instead of importing a production helper or exercising the Express middleware, so it is weaker than an integration test would be.
  • The intended fallback 200-acknowledges and drops unrecoverable authenticated malformed payloads; that tradeoff should be explicitly accepted by the MS Teams maintainer.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 554b32feeac0.

@vincentkoc vincentkoc added the stale Marked as stale due to inactivity label Apr 26, 2026
@vincentkoc
Copy link
Copy Markdown
Member

This assigned pull request has been marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 27, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 27, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 28, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 28, 2026
@hddevteam hddevteam force-pushed the fix/msteams-json-escape-sequences branch from d14ee15 to 2e72f13 Compare April 28, 2026 16:23
hddevteam and others added 5 commits April 29, 2026 07:28
…ctivities

Some Bot Framework clients (e.g. certain MS Teams clients) send activity
payloads that contain invalid JSON escape sequences such as bare backslashes
followed by characters not defined in RFC 8259 (e.g. \p, \q, \c).

The previous `express.json()` middleware uses body-parser's strict JSON
parser which throws `SyntaxError: Bad escaped character in JSON` on such
payloads. This causes the webhook to return a non-200 response, putting
Azure Bot Service into exponential backoff and dropping subsequent messages.

This fix replaces `express.json()` with `express.raw()` (raw Buffer body),
then adds a two-pass JSON parse middleware:

1. First tries standard JSON.parse on the raw body.
2. If that fails (SyntaxError), attempts to repair the payload by
   double-escaping bare backslashes: /\\([^"\\/bfnrtu])/g → \\\\$1
3. If the repaired payload parses successfully, logs a warning and
   continues normally so the activity is processed.
4. If the payload cannot be repaired, responds with HTTP 200 to prevent
   Azure Bot Service backoff, and logs the error for investigation.

Fixes: SyntaxError: Bad escaped character in JSON at position N
Tested: conversationUpdate and message activities from MS Teams clients
…iddleware

Covers the bug where express.json()'s strict parser throws SyntaxError on
Bot Framework activities containing invalid escape sequences (e.g. \\p, \\q),
causing Azure Bot Service to enter exponential backoff and drop messages.

Tests verify:
1. Original JSON.parse() fails on bad-escape payloads (proving the bug)
2. Two-pass repair middleware recovers such payloads
3. Normal well-formed payloads still work (no regression)
4. Valid escape sequences (\\n \\t \\\\ etc.) are not double-escaped
5. Completely unrecoverable JSON is re-thrown (caller returns HTTP 200)"
Copilot Reviewer identified that the express mock in monitor.lifecycle.test.ts
exported json() but not raw(). Since monitor.ts now uses express.raw() instead
of express.json(), the test suite would throw 'express.raw is not a function'
at startup.

Add raw() as a no-op middleware factory alongside json() in the mock, matching
the pattern already used for json().
CI 'Check types and lint and oxfmt' step failed because our new files
were not formatted with oxfmt. Run oxfmt --write on both files to fix.
@hddevteam hddevteam force-pushed the fix/msteams-json-escape-sequences branch from 2e72f13 to 7155816 Compare April 28, 2026 23:29
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 29, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 29, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 30, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 30, 2026
@barnacle-openclaw
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: M stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants