VYPR
High severity8.9NVD Advisory· Published Jun 10, 2024· Updated Apr 15, 2026

CVE-2024-37166

CVE-2024-37166

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ghtmlnpm
< 2.0.02.0.0

Patches

1
df1ea50fe896

Merge pull request from GHSA-vvhj-v88f-5gxr

https://github.com/gurgunday/ghtmlGürgün DayıoğluJun 10, 2024via ghsa
4 files changed · +28 22
  • package.json+2 2 modified
    @@ -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": {
    
  • README.md+8 4 modified
    @@ -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.
    
  • src/html.js+7 5 modified
    @@ -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;
    
  • test/index.js+11 11 modified
    @@ -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");
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.