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.
| Package | Affected versions | Patched versions |
|---|---|---|
ghtmlnpm | < 2.0.0 | 2.0.0 |
Patches
1df1ea50fe896Merge pull request from GHSA-vvhj-v88f-5gxr
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, <img src="https://example.com/hacker.png"></h1> +// Output: <h1>Hello, <img src="https://example.com/pwned.png"></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 = { - '"': """, - "'": "'", - "&": "&", - "<": "<", - ">": ">", + '"': """, + "&": "&", + "'": "'", + "<": "<", + ">": ">", + "`": "`", }; 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><script>alert('This is an unsafe description.')</script></p>`, + `<p><script>alert('This is an unsafe description.')</script></p>`, ); }); test("renders arrays", () => { assert.strictEqual( html`<p>${[descriptionSafe, descriptionUnsafe]}</p>`, - "<p>This is a safe description.<script>alert('This is an unsafe description.')</script></p>", + "<p>This is a safe description.<script>alert('This is an unsafe description.')</script></p>", ); }); @@ -81,7 +81,7 @@ test("renders nested html calls", () => { // prettier-ignore assert.strictEqual( html`<p>!${conditionTrue ? html`<strong>${descriptionUnsafe}</strong>` : ""}</p>`, - "<p><strong><script>alert('This is an unsafe description.')</script></strong></p>", + "<p><strong><script>alert('This is an unsafe description.')</script></strong></p>", ); }); @@ -156,7 +156,7 @@ test("htmlGenerator renders unsafe content", () => { assert.strictEqual( accumulator, - "<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>", + "<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>", ); }); @@ -199,7 +199,7 @@ test("htmlGenerator works with other generators (escaped)", () => { assert.strictEqual( accumulator, - "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></div>", + "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></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><p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p></div>", + "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p></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.<script>alert('This is an unsafe description.')</script>12345255</p>", + "<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>", ); }); @@ -286,7 +286,7 @@ test("htmlAsyncGenerator works with other generators (escaped)", async () => { assert.strictEqual( accumulator, - "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></div>", + "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></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></p>2: <p># test.md></p>3: <p># test.md></p>", + "1: <p># test.md></p>2: <p># test.md></p>3: <p># test.md></p>", ); }); @@ -312,7 +312,7 @@ test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => { })}</ul>`; const fileContent = readFileSync("test/test.md", "utf8").replaceAll( ">", - ">", + ">", ); 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, "<p>"); + assert.strictEqual(value.value, "<p>"); 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
4News mentions
0No linked articles in our index yet.