CVE-2024-37166: Insufficient Information (NVD-CWE-noinfo)
value: ghtml is software that uses tagged templates for template engine functionality. It is possible to introduce user-controlled JavaScript code and trigger a Cross-Site Scripting (XSS) vulnerability in some cases. Version 2.0.0 introduces changes to mitigate this issue. Version 2.0.0 contains updated documentation to clarify that while ghtml escapes characters with special meaning in HTML, it does not provide comprehensive protection against all types of XSS attacks in every scenario. This aligns with the approach taken by other template engines. Developers should be cautious and take additional measures to sanitize user input and prevent potential vulnerabilities. Additionally, the backtick character (`) is now also escaped to prevent the creation of strings in most cases where a malicious actor somehow gains the ability to write JavaScript. This does not provide comprehensive protection either.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-06-10T22:15Z
Repository: https://github.com/gurgunday/ghtml
Language: JavaScript
Files changed: README.md, package.json, src/html.js, test/index.js
Commit date: 2024-06-10 18:16:45 +0300
Commit message: Merge pull request from GHSA-vvhj-v88f-5gxr

* add = to escaped chars

* add Security to README and remove XSS protection

* add `

* remove =

* format

* improve readme.md

* update

* update README.md
Diff: diff --git a/README.md b/README.md
index 305f7e2..5768ce3 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@ npm i ghtml
 
 ## API Reference
 
 ### `html`
 
-The `html` function is designed to tag template literals and automatically escape their expressions to prevent XSS attacks. To intentionally bypass escaping for a specific expression, prefix it with `!`.
+The `html` function is designed to tag template literals and automatically escape their expressions. To intentionally bypass escaping a specific expression, prefix it with `!`.
 
 ### `htmlGenerator`
 
 The `htmlGenerator` function acts as the generator version of the `html` function. It facilitates the creation of HTML fragments iteratively, making it ideal for parsing large templates or constructing HTML content dynamically.
 
@@ -30,24 +30,24 @@ This version of HTML generator should be preferred for asynchronous and streamin
 
 Because they return generators instead of strings, a key difference of `htmlGenerator` and `htmlAsyncGenerator` is their ability to recognize and properly handle iterable elements within array expressions. This is to detect nested `htmlGenerator` and `htmlAsyncGenerator` usage, enabling scenarios such as ``${[1, 2, 3].map(i => htmlGenerator`<li>${i}</li>`)}``.
 
 ### `includeFile`
 
-Available for Node.js users, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
+Available in Node.js, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
 
 ## Usage
 
 ### `html`
 
 ```js
 import { html } from "ghtml";
 
-const username = '<img src="https://example.com/hacker.png">';
+const username = '<img src="https://example.com/pwned.png">';
 const greeting = html`<h1>Hello, ${username}!</h1>`;
 
 console.log(greeting);
-// Output: <h1>Hello, &lt;img src=&quot;https://example.com/hacker.png&quot;&gt;</h1>
+// Output: <h1>Hello, &#60;img src=&#34;https://example.com/pwned.png&#34;&#62;</h1>
 ```
 
 To bypass escaping:
 
 ```js
@@ -166,5 +166,9 @@ import { includeFile } from "ghtml/includeFile.js";
 const logo = includeFile("static/logo.svg");
 
 console.log(logo);
 // Output: content of "static/logo.svg"
 ```
+
+## Security
+
+Like [similar](https://handlebarsjs.com/guide/#html-escaping) [tools](https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities), `ghtml` will not prevent all kinds of XSS attacks. It is the responsibility of consumers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values (especially when they contain expressions), and using unsanitized user-provided URLs.
diff --git a/package.json b/package.json
index 619d7d2..4b8e0d4 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
 {
   "name": "ghtml",
   "description": "Replace your template engine with fast JavaScript by leveraging the power of tagged templates.",
   "author": "Gürgün Dayıoğlu",
   "license": "MIT",
-  "version": "1.7.2",
+  "version": "2.0.0",
   "type": "module",
   "main": "./src/index.js",
   "exports": {
     ".": "./src/index.js",
     "./*.js": "./src/*.js"
@@ -20,11 +20,11 @@
     "lint:fix": "eslint --fix . && prettier --write ."
   },
   "devDependencies": {
     "@fastify/pre-commit": "^2.1.0",
     "c8": "^9.1.0",
-    "grules": "^0.17.1",
+    "grules": "^0.17.2",
     "tinybench": "^2.8.0"
   },
   "repository": {
     "type": "git",
     "url": "git+https://github.com/gurgunday/ghtml.git"
diff --git a/src/html.js b/src/html.js
index d9719dd..3849b70 100644
--- a/src/html.js
+++ b/src/html.js
@@ -1,11 +1,12 @@
 const escapeDictionary = {
-  '"': "&quot;",
-  "'": "&apos;",
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
+  '"': "&#34;",
+  "&": "&#38;",
+  "'": "&#39;",
+  "<": "&#60;",
+  ">": "&#62;",
+  "`": "&#96;",
 };
 
 const escapeRegExp = new RegExp(
   `[${Object.keys(escapeDictionary).join("")}]`,
   "u",
@@ -17,10 +18,11 @@ const escapeFunction = (string) => {
   let end = 0;
   let escaped = "";
 
   do {
     const escapedCharacter = escapeDictionary[string[end++]];
+
     if (escapedCharacter) {
       escaped += string.slice(start, end - 1) + escapedCharacter;
       start = end;
     }
   } while (end !== stringLength);
diff --git a/test/index.js b/test/index.js
index 4b1358e..8357a8e 100644
--- a/test/index.js
+++ b/test/index.js
@@ -57,18 +57,18 @@ test("renders safe content", () => {
 });
 
 test("renders unsafe content", () => {
   assert.strictEqual(
     html`<p>${descriptionUnsafe}</p>`,
-    `<p>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>`,
+    `<p>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>`,
   );
 });
 
 test("renders arrays", () => {
   assert.strictEqual(
     html`<p>${[descriptionSafe, descriptionUnsafe]}</p>`,
-    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>",
+    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>",
   );
 });
 
 test("bypasses escaping", () => {
   assert.strictEqual(
@@ -79,11 +79,11 @@ test("bypasses escaping", () => {
 
 test("renders nested html calls", () => {
   // prettier-ignore
   assert.strictEqual(
     html`<p>!${conditionTrue ? html`<strong>${descriptionUnsafe}</strong>` : ""}</p>`,
-    "<p><strong>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</strong></p>",
+    "<p><strong>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</strong></p>",
   );
 });
 
 test("renders multiple html calls", () => {
   assert.strictEqual(
@@ -154,11 +154,11 @@ test("htmlGenerator renders unsafe content", () => {
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator,
-    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
+    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
   );
 });
 
 test("htmlGenerator works with nested htmlGenerator calls in an array", () => {
   const generator = htmlGenerator`<ul>!${[1, 2, 3].map((index) => {
@@ -197,11 +197,11 @@ test("htmlGenerator works with other generators (escaped)", () => {
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator,
-    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
+    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
   );
   assert.strictEqual(generator.next().done, true);
 });
 
 test("htmlGenerator works with other generators within an array (raw)", () => {
@@ -227,11 +227,11 @@ test("htmlGenerator works with other generators within an array (escaped)", () =
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator,
-    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;1,2,3,4,5255&lt;/p&gt;</div>",
+    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;1,2,3,4,5255&#60;/p&#62;</div>",
   );
   assert.strictEqual(generator.next().done, true);
 });
 
 test("htmlAsyncGenerator renders safe content", async () => {
@@ -256,11 +256,11 @@ test("htmlAsyncGenerator renders unsafe content", async () => {
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator,
-    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
+    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
   );
 });
 
 test("htmlAsyncGenerator works with other generators (raw)", async () => {
   const generator = htmlAsyncGenerator`<div>!${generatorExample()}</div>`;
@@ -284,11 +284,11 @@ test("htmlAsyncGenerator works with other generators (escaped)", async () => {
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator,
-    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
+    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
   );
 });
 
 test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array", async () => {
   const generator = htmlAsyncGenerator`!${[1, 2, 3].map((i) => {
@@ -300,21 +300,21 @@ test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array"
     accumulator += value;
   }
 
   assert.strictEqual(
     accumulator.replaceAll("\n", "").trim(),
-    "1: <p># test.md&gt;</p>2: <p># test.md&gt;</p>3: <p># test.md&gt;</p>",
+    "1: <p># test.md&#62;</p>2: <p># test.md&#62;</p>3: <p># test.md&#62;</p>",
   );
 });
 
 test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => {
   const generator = htmlAsyncGenerator`<ul>!${[1, 2].map((i) => {
     return htmlAsyncGenerator`${i}: ${readFile("test/test.md", "utf8")}`;
   })}</ul>`;
   const fileContent = readFileSync("test/test.md", "utf8").replaceAll(
     ">",
-    "&gt;",
+    "&#62;",
   );
 
   let value = await generator.next();
   assert.strictEqual(value.value, "<ul>");
 
@@ -370,11 +370,11 @@ test("htmlAsyncGenerator redners in chuncks", async () => {
 
   let value = await generator.next();
   assert.strictEqual(value.value, "<ul>");
 
   value = await generator.next();
-  assert.strictEqual(value.value, "&lt;p&gt;");
+  assert.strictEqual(value.value, "&#60;p&#62;");
 
   value = await generator.next();
   assert.strictEqual(value.value, "12");
 
   value = await generator.next();

Vulnerable code: The `html` function is designed to tag template literals and automatically escape their expressions to prevent XSS attacks. To intentionally bypass escaping for a specific expression, prefix it with `!`.
Available for Node.js users, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
const username = '<img src="https://example.com/hacker.png">';
// Output: <h1>Hello, &lt;img src=&quot;https://example.com/hacker.png&quot;&gt;</h1>
  "version": "1.7.2",
    "grules": "^0.17.1",
  '"': "&quot;",
  "'": "&apos;",
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
    `<p>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>`,
    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>",
    "<p><strong>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</strong></p>",
    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;1,2,3,4,5255&lt;/p&gt;</div>",
    "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
    "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
    "1: <p># test.md&gt;</p>2: <p># test.md&gt;</p>3: <p># test.md&gt;</p>",
    "&gt;",
  assert.strictEqual(value.value, "&lt;p&gt;");
Fixed code: The `html` function is designed to tag template literals and automatically escape their expressions. To intentionally bypass escaping a specific expression, prefix it with `!`.
Available in Node.js, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
const username = '<img src="https://example.com/pwned.png">';
// Output: <h1>Hello, &#60;img src=&#34;https://example.com/pwned.png&#34;&#62;</h1>

## Security

Like [similar](https://handlebarsjs.com/guide/#html-escaping) [tools](https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities), `ghtml` will not prevent all kinds of XSS attacks. It is the responsibility of consumers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values (especially when they contain expressions), and using unsanitized user-provided URLs.
  "version": "2.0.0",
    "grules": "^0.17.2",
  '"': "&#34;",
  "&": "&#38;",
  "'": "&#39;",
  "<": "&#60;",
  ">": "&#62;",
  "`": "&#96;",

    `<p>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>`,
    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>",
    "<p><strong>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</strong></p>",
    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;1,2,3,4,5255&#60;/p&#62;</div>",
    "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
    "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
    "1: <p># test.md&#62;</p>2: <p># test.md&#62;</p>3: <p># test.md&#62;</p>",
    "&#62;",
  assert.strictEqual(value.value, "&#60;p&#62;");