Skip to content

Commit

Permalink
Merge pull request from GHSA-vvhj-v88f-5gxr
Browse files Browse the repository at this point in the history
* add = to escaped chars

* add Security to README and remove XSS protection

* add `

* remove =

* format

* improve readme.md

* update

* update README.md
  • Loading branch information
gurgunday authored Jun 10, 2024
1 parent b8e5889 commit df1ea50
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 22 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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": {
Expand Down
12 changes: 7 additions & 5 deletions src/html.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const escapeDictionary = {
'"': "&quot;",
"'": "&apos;",
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&#34;",
"&": "&#38;",
"'": "&#39;",
"<": "&#60;",
">": "&#62;",
"`": "&#96;",
};

const escapeRegExp = new RegExp(
Expand All @@ -19,6 +20,7 @@ const escapeFunction = (string) => {

do {
const escapedCharacter = escapeDictionary[string[end++]];

if (escapedCharacter) {
escaped += string.slice(start, end - 1) + escapedCharacter;
start = end;
Expand Down
22 changes: 11 additions & 11 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>",
);
});

Expand All @@ -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>",
);
});

Expand Down Expand Up @@ -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>",
);
});

Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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>",
);
});

Expand Down Expand Up @@ -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>",
);
});

Expand All @@ -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>",
);
});

Expand All @@ -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();
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit df1ea50

Please sign in to comment.