Vulnerability: jsx-slack insufficient patch for CVE-2021-43838 ReDoS We found the patch for CVE-2021-43838 in jsx-slack v4.5.1 is insufficient to save from Regular Expression Denial of Service (ReDoS) attack.

This vulnerability affects to jsx-slack v4.5.1 and earlier versions.

### Impact

If attacker can put a lot of JSX elements into `<blockquote>` tag _with including multibyte characters_, an internal regular expression for escaping characters may consume an excessive amount of computing resources.

```javascript
/** @jsxImportSource jsx-slack */
import { Section } from 'jsx-slack'

console.log(
  <Section>
    <blockquote>
      {[...Array(40)].map(() => (
        <p>亜</p>
      ))}
    </blockquote>
  </Section>
)
```

v4.5.1 has released by passing the test against ASCII characters but missed the case of multibyte characters.
https://github.com/yhatt/jsx-slack/security/advisories/GHSA-55xv-f85c-248q

### Patches

jsx-slack v4.5.2 has updated regular expressions for escaping blockquote characters to prevent catastrophic backtracking. It is also including an updated test case to confirm rendering multiple tags in `<blockquote>` with multibyte characters.

### References

- https://github.com/yhatt/jsx-slack/commit/46bc88391d89d5fda4ce689e18ca080bcdd29ecc

### Credits

Thanks to @hieki for finding out this vulnerability.
Vulnerability ID: GHSA-hp68-xhvj-x6j6
CWE: CWE-400
Severity score: 5.3
Project: https://github.com/yhatt/jsx-slack
Published: 2022-01-06
File: src/mrkdwn/escape.ts
Commit message: Prevent catastrophic backtracking in blockquote escape replacer
Commit author: Yuki Hattori

Vulnerable version (before fix):
 // An internal HTML tag and emoji shorthand should not escape const preventEscapeRegex =   /(<.*?>|:[-a-z0-9ÀÁÂÃÄÇÈÉÊËÍÎÏÑÓÔÕÖŒœÙÚÛÜŸßàáâãäçèéêëíîïñóôõöùúûüÿ_＿+＋'\u1100-\u11ff\u2e80-\u2fd5\u3005\u3041-\u3096\u30a0-\u30ff\u3130-\u318f\u3400-\u4db5\u4e00-\u9fcb\ua960-\ua97f\uac00-\ud7ff\uff10-\uff19\uff41-\uff5a\uff61-\uff9f]+:)/  const generateReplacerForEscape = (fallback: string) => (matched: string) =>   `<span data-escape="${fallback.repeat(matched.length)}">${matched}</span>`  export const escapeReplacers = {   blockquote: (partial: string) =>     partial       .replace(/^((?:<.*?>)*)(.{4})/gm, (matched, leading, character) =>         character === '&gt;' ? `${leading}\u00ad&gt;` : matched       )       .replace(         /^((?:<.*?>)*)(＞)/gm,         (_, leading, character) =>           `${leading}${generateReplacerForEscape('\u00ad＞')(character)}`       ),   bold: (partial: string) =>     partial

Fixed version (after fix):
 // An internal HTML tag and emoji shorthand should not escape const preventEscapeRegex =   /(<[^>]*>|:[-a-z0-9ÀÁÂÃÄÇÈÉÊËÍÎÏÑÓÔÕÖŒœÙÚÛÜŸßàáâãäçèéêëíîïñóôõöùúûüÿ_＿+＋'\u1100-\u11ff\u2e80-\u2fd5\u3005\u3041-\u3096\u30a0-\u30ff\u3130-\u318f\u3400-\u4db5\u4e00-\u9fcb\ua960-\ua97f\uac00-\ud7ff\uff10-\uff19\uff41-\uff5a\uff61-\uff9f]+:)/  const generateReplacerForEscape = (fallback: string) => (matched: string) =>   `<span data-escape="${fallback.repeat(matched.length)}">${matched}</span>`  export const escapeReplacers = {   blockquote: (partial: string) =>     partial       .replace(         /^((?:<(?:[^>]|>(?=<))*>)?)(&gt;)/gm,         (_, leadingTags, character) => `${leadingTags}\u00ad${character}`       )       .replace(         /^((?:<(?:[^>]|>(?=<))*>)?)(＞)/gm,         (_, leadingTags, character) =>           `${leadingTags}${generateReplacerForEscape('\u00ad＞')(character)}`       ),   bold: (partial: string) =>     partial

Patch:
patch: @@ -1,20 +1,21 @@
 // An internal HTML tag and emoji shorthand should not escape
 const preventEscapeRegex =
-  /(<.*?>|:[-a-z0-9ÀÁÂÃÄÇÈÉÊËÍÎÏÑÓÔÕÖŒœÙÚÛÜŸßàáâãäçèéêëíîïñóôõöùúûüÿ_＿+＋'\u1100-\u11ff\u2e80-\u2fd5\u3005\u3041-\u3096\u30a0-\u30ff\u3130-\u318f\u3400-\u4db5\u4e00-\u9fcb\ua960-\ua97f\uac00-\ud7ff\uff10-\uff19\uff41-\uff5a\uff61-\uff9f]+:)/
+  /(<[^>]*>|:[-a-z0-9ÀÁÂÃÄÇÈÉÊËÍÎÏÑÓÔÕÖŒœÙÚÛÜŸßàáâãäçèéêëíîïñóôõöùúûüÿ_＿+＋'\u1100-\u11ff\u2e80-\u2fd5\u3005\u3041-\u3096\u30a0-\u30ff\u3130-\u318f\u3400-\u4db5\u4e00-\u9fcb\ua960-\ua97f\uac00-\ud7ff\uff10-\uff19\uff41-\uff5a\uff61-\uff9f]+:)/
 
 const generateReplacerForEscape = (fallback: string) => (matched: string) =>
   `<span data-escape="${fallback.repeat(matched.length)}">${matched}</span>`
 
 export const escapeReplacers = {
   blockquote: (partial: string) =>
     partial
-      .replace(/^((?:<.*?>)*)(.{4})/gm, (matched, leading, character) =>
-        character === '&gt;' ? `${leading}\u00ad&gt;` : matched
+      .replace(
+        /^((?:<(?:[^>]|>(?=<))*>)?)(&gt;)/gm,
+        (_, leadingTags, character) => `${leadingTags}\u00ad${character}`
       )
       .replace(
-        /^((?:<.*?>)*)(＞)/gm,
-        (_, leading, character) =>
-          `${leading}${generateReplacerForEscape('\u00ad＞')(character)}`
+        /^((?:<(?:[^>]|>(?=<))*>)?)(＞)/gm,
+        (_, leadingTags, character) =>
+          `${leadingTags}${generateReplacerForEscape('\u00ad＞')(character)}`
       ),
   bold: (partial: string) =>
     partial
path: src/mrkdwn/escape.ts