CVE-2021-43785: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: @joeattardi/emoji-button is a Vanilla JavaScript emoji picker component. In affected versions there are two vectors for XSS attacks: a URL for a custom emoji, and an i18n string. In both of these cases, a value can be crafted such that it can insert a `script` tag into the page and execute malicious code.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2021-11-26T19:15Z
Repository: https://github.com/joeattardi/emoji-button
Language: TypeScript
Files changed: src/categoryButtons.ts, src/emojiArea.ts, src/preview.ts, src/search.ts
Commit date: 2021-11-24 22:49:23 -0500
Commit message: Add more HTML escaping

Diff: diff --git a/src/categoryButtons.ts b/src/categoryButtons.ts
index d59b9ce..81c123f 100644
--- a/src/categoryButtons.ts
+++ b/src/categoryButtons.ts
@@ -1,6 +1,7 @@
 import { TinyEmitter as Emitter } from 'tiny-emitter';
+import escape from 'escape-html';
 
 import { CLASS_CATEGORY_BUTTONS, CLASS_CATEGORY_BUTTON } from './classes';
 
 import emojiData from './data/emoji';
 
diff --git a/src/emojiArea.ts b/src/emojiArea.ts
index 33facbd..3166993 100644
--- a/src/emojiArea.ts
+++ b/src/emojiArea.ts
@@ -1,6 +1,7 @@
 import { TinyEmitter as Emitter } from 'tiny-emitter';
+import escape from 'escape-html';
 
 import emojiData from './data/emoji';
 import { i18n as defaultI18n } from './i18n';
 
 import {
@@ -295,12 +296,13 @@ export class EmojiArea {
   private addCategory = (
     category: string,
     emojis: Array<EmojiRecord | RecentEmoji>
   ): void => {
     const name = createElement('h2', CLASS_CATEGORY_NAME);
-    name.innerHTML =
-      this.i18n.categories[category] || defaultI18n.categories[category];
+    name.innerHTML = escape(
+      this.i18n.categories[category] || defaultI18n.categories[category]
+    );
     this.emojis.appendChild(name);
     this.headers.push(name);
 
     this.emojis.appendChild(
       new EmojiContainer(
diff --git a/src/preview.ts b/src/preview.ts
index 0d6ec56..da6a210 100644
--- a/src/preview.ts
+++ b/src/preview.ts
@@ -47,11 +47,11 @@ export class EmojiPreview {
     } else if (this.options.style === 'twemoji') {
       content = twemoji.parse(emoji.emoji, this.options.twemojiOptions);
     }
 
     this.emoji.innerHTML = content;
-    this.name.innerHTML = emoji.name;
+    this.name.innerHTML = escape(emoji.name);
   }
 
   hidePreview(): void {
     this.emoji.innerHTML = '';
     this.name.innerHTML = '';
diff --git a/src/search.ts b/src/search.ts
index a1c2f31..4652ed3 100644
--- a/src/search.ts
+++ b/src/search.ts
@@ -1,6 +1,7 @@
 import { TinyEmitter as Emitter } from 'tiny-emitter';
+import escape from 'escape-html';
 
 import * as icons from './icons';
 
 import { EmojiContainer } from './emojiContainer';
 import {
@@ -38,11 +39,11 @@ class NotFoundMessage {
     }
 
     container.appendChild(iconContainer);
 
     const messageContainer = createElement('h2');
-    messageContainer.innerHTML = this.message;
+    messageContainer.innerHTML = escape(this.message);
     container.appendChild(messageContainer);
 
     return container;
   }
 }

Vulnerable code:     name.innerHTML =
      this.i18n.categories[category] || defaultI18n.categories[category];
    this.name.innerHTML = emoji.name;
    messageContainer.innerHTML = this.message;
Fixed code: import escape from 'escape-html';
import escape from 'escape-html';
    name.innerHTML = escape(
      this.i18n.categories[category] || defaultI18n.categories[category]
    );
    this.name.innerHTML = escape(emoji.name);
import escape from 'escape-html';
    messageContainer.innerHTML = escape(this.message);