CVE-2024-37166 (published 2024-06-10T22:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/gurgunday/ghtml
commit_details: repo_url: https://github.com/gurgunday/ghtml
author: Gürgün Dayıoğlu
author_date: 2024-06-10 18:16:45+03:00
msg: 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
repository: repo_url: https://github.com/gurgunday/ghtml
repo_name: gurgunday/ghtml
description: Replace your template engine (including JSX/React) with fast JavaScript
repo_language: JavaScript
owner: gurgunday
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -12,7 +12,7 @@ npm i ghtml
 
 ### `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`
 
@@ -32,7 +32,7 @@ Because they return generators instead of strings, a key difference of `htmlGene
 
 ### `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
 
@@ -41,11 +41,11 @@ Available for Node.js users, the `includeFile` function is a wrapper around `rea
 ```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:
@@ -168,3 +168,7 @@ 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.

programming_language: Markdown
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@
   "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": {
@@ -22,7 +22,7 @@
   "devDependencies": {
     "@fastify/pre-commit": "^2.1.0",
     "c8": "^9.1.0",
-    "grules": "^0.17.1",
+    "grules": "^0.17.2",
     "tinybench": "^2.8.0"
   },
   "repository": {

programming_language: JavaScript
filename: html.js
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,10 @@
 const escapeDictionary = {
-  '"': "&quot;",
-  "'": "&apos;",
-  "&": "&amp;",
-  "<": "&lt;",
-  ">": "&gt;",
+  '"': "&#34;",
+  "&": "&#38;",
+  "'": "&#39;",
+  "<": "&#60;",
+  ">": "&#62;",
+  "`": "&#96;",
 };
 
 const escapeRegExp = new RegExp(
@@ -19,6 +20,7 @@ const escapeFunction = (string) => {
 
   do {
     const escapedCharacter = escapeDictionary[string[end++]];
+
     if (escapedCharacter) {
       escaped += string.slice(start, end - 1) + escapedCharacter;
       start = end;

programming_language: TypeScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -59,14 +59,14 @@ 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>",
   );
 });
 
@@ -81,7 +81,7 @@ 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>",
   );
 });
 
@@ -156,7 +156,7 @@ test("htmlGenerator renders unsafe content", () => {
 
   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>",
   );
 });
 
@@ -199,7 +199,7 @@ test("htmlGenerator works with other generators (escaped)", () => {
 
   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);
 });
@@ -229,7 +229,7 @@ test("htmlGenerator works with other generators within an array (escaped)", () =
 
   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);
 });
@@ -258,7 +258,7 @@ test("htmlAsyncGenerator renders unsafe content", async () => {
 
   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>",
   );
 });
 
@@ -286,7 +286,7 @@ test("htmlAsyncGenerator works with other generators (escaped)", async () => {
 
   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>",
   );
 });
 
@@ -302,7 +302,7 @@ test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array"
 
   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>",
   );
 });
 
@@ -312,7 +312,7 @@ test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => {
   })}</ul>`;
   const fileContent = readFileSync("test/test.md", "utf8").replaceAll(
     ">",
-    "&gt;",
+    "&#62;",
   );
 
   let value = await generator.next();
@@ -372,7 +372,7 @@ test("htmlAsyncGenerator redners in chuncks", async () => {
   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");

programming_language: JavaScript