CVE-2022-31103: Improper Check for Unusual or Exceptional Conditions (CWE-754)
value: lettersanitizer is a DOM-based HTML email sanitizer for in-browser email rendering. All versions of lettersanitizer below 1.0.2 are affected by a denial of service issue when processing a CSS at-rule `@keyframes`. This package is depended on by [react-letter](https://github.com/mat-sz/react-letter), therefore everyone using react-letter is also at risk. The problem has been patched in version 1.0.2.
CWE description: The product does not check or incorrectly checks for unusual or exceptional conditions that are not expected to occur frequently during day to day operation of the product.
CVSS v3 base score: 7.5
Published: 2022-06-27T23:15Z
Repository: https://github.com/mat-sz/lettersanitizer
Language: TypeScript
Files changed: __tests__/sanitize.test.ts, src/index.ts
Commit date: 2022-06-22 18:10:29 +0200
Commit message: filter out unsupported at-rules

Diff: diff --git a/__tests__/sanitize.test.ts b/__tests__/sanitize.test.ts
index 324ba95..09c2adc 100644
--- a/__tests__/sanitize.test.ts
+++ b/__tests__/sanitize.test.ts
@@ -293,6 +293,61 @@ b {background: red;}</style>`,
         { noWrapper: true, preserveCssPriority: false }
       )
     ).toBe(`<style>a {background: red;}
 b {background: red;}</style>`);
   });
+
+  it('removes @keyframes rules from CSS', () => {
+    expect(
+      sanitize(
+        `<style>@keyframes test {
+          0% {
+              transform: rotate(0deg);
+          }
+
+          100% {
+              transform: rotate(360deg);
+          }
+        }</style>`,
+        '',
+        { noWrapper: true, preserveCssPriority: false }
+      )
+    ).toBe(`<style></style>`);
+  });
+
+  it('removes @supports rules from CSS', () => {
+    expect(
+      sanitize(
+        `<style>@supports (display: grid) {
+            div {
+              display: grid;
+            }
+          }</style>`,
+        '',
+        { noWrapper: true, preserveCssPriority: false }
+      )
+    ).toBe(`<style></style>`);
+  });
+
+  it('removes @font-face rules from CSS', () => {
+    expect(
+      sanitize(
+        `<style>@font-face {
+            font-family: "Open Sans";
+            src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
+                 url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
+          }</style>`,
+        '',
+        { noWrapper: true, preserveCssPriority: false }
+      )
+    ).toBe(`<style></style>`);
+  });
+
+  it('removes @import rules from CSS', () => {
+    expect(
+      sanitize(`<style>@import 'custom.css';</style>`, '', {
+        noWrapper: true,
+        preserveCssPriority: false,
+      })
+    ).toBe(`<style></style>`);
+  });
 });
diff --git a/src/index.ts b/src/index.ts
index 530a885..96ca01a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -305,11 +305,13 @@ function sanitizeHtml(
           allowedSchemas,
           preserveCssPriority,
           rewriteExternalResources
         );
         newRules.push(rule);
-      } else if ('cssRules' in rule) {
+      } else if ('cssRules' in rule && 'media' in rule) {
+        // According to https://www.caniemail.com/,
+        // out of all at-rules, Gmail only supports @media.
         const mediaRule = (rule as any) as CSSMediaRule;
         const newRulesMedia: CSSRule[] = [];
 
         for (let i = 0; i < mediaRule.cssRules.length; i++) {
           const rule = mediaRule.cssRules[i] as CSSStyleRule;

Vulnerable code:       } else if ('cssRules' in rule) {
Fixed code: 
  it('removes @keyframes rules from CSS', () => {
    expect(
      sanitize(
        `<style>@keyframes test {
          0% {
              transform: rotate(0deg);
          }

          100% {
              transform: rotate(360deg);
          }
        }</style>`,
        '',
        { noWrapper: true, preserveCssPriority: false }
      )
    ).toBe(`<style></style>`);
  });

  it('removes @supports rules from CSS', () => {
    expect(
      sanitize(
        `<style>@supports (display: grid) {
            div {
              display: grid;
            }
          }</style>`,
        '',
        { noWrapper: true, preserveCssPriority: false }
      )
    ).toBe(`<style></style>`);
  });

  it('removes @font-face rules from CSS', () => {
    expect(
      sanitize(
        `<style>@font-face {
            font-family: "Open Sans";
            src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
                 url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
          }</style>`,
        '',
        { noWrapper: true, preserveCssPriority: false }
      )
    ).toBe(`<style></style>`);
  });

  it('removes @import rules from CSS', () => {
    expect(
      sanitize(`<style>@import 'custom.css';</style>`, '', {
        noWrapper: true,
        preserveCssPriority: false,
      })
    ).toBe(`<style></style>`);
  });
      } else if ('cssRules' in rule && 'media' in rule) {
        // According to https://www.caniemail.com/,
        // out of all at-rules, Gmail only supports @media.