chore: upgrade marked 15→17 (ReDoS fix, list rendering improvements)
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 17m28s

This commit is contained in:
Hoid 2026-03-11 08:07:05 +01:00
parent af3391d05a
commit 75c6a6ce58
3 changed files with 117 additions and 6 deletions

10
package-lock.json generated
View file

@ -12,7 +12,7 @@
"express": "^4.21.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.0.0",
"marked": "^15.0.0",
"marked": "^17.0.4",
"nanoid": "^5.0.0",
"nodemailer": "^8.0.2",
"pg": "^8.20.0",
@ -2953,15 +2953,15 @@
}
},
"node_modules/marked": {
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"version": "17.0.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
"integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
"node": ">= 20"
}
},
"node_modules/math-intrinsics": {

View file

@ -16,7 +16,7 @@
"express": "^4.21.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.0.0",
"marked": "^15.0.0",
"marked": "^17.0.4",
"nanoid": "^5.0.0",
"nodemailer": "^8.0.2",
"pg": "^8.20.0",

View file

@ -0,0 +1,111 @@
import { describe, it, expect } from "vitest";
import { marked } from "marked";
/** Tests for marked list rendering — covering v17 breaking changes */
describe("Markdown list rendering", () => {
const parse = (md: string) => marked.parse(md, { async: false }) as string;
describe("loose lists (paragraphs inside list items)", () => {
it("renders loose list items with <p> tags", () => {
const md = `- Item one\n\n- Item two\n\n- Item three\n`;
const html = parse(md);
expect(html).toContain("<ul>");
expect(html).toContain("<li>");
// Loose lists wrap content in <p>
expect(html).toContain("<p>Item one</p>");
expect(html).toContain("<p>Item two</p>");
expect(html).toContain("<p>Item three</p>");
});
it("renders tight list items without <p> tags", () => {
const md = `- Item one\n- Item two\n- Item three\n`;
const html = parse(md);
expect(html).toContain("<ul>");
expect(html).not.toContain("<p>");
expect(html).toContain("Item one");
});
});
describe("checkbox/task lists", () => {
it("renders unchecked checkboxes", () => {
const md = `- [ ] Todo item\n`;
const html = parse(md);
expect(html).toContain('<input');
expect(html).toContain('type="checkbox"');
expect(html).not.toContain("checked");
expect(html).toContain("Todo item");
});
it("renders checked checkboxes", () => {
const md = `- [x] Done item\n`;
const html = parse(md);
expect(html).toContain('checked');
expect(html).toContain("Done item");
});
it("renders mixed task list", () => {
const md = `- [x] Done\n- [ ] Pending\n- Regular item\n`;
const html = parse(md);
expect(html).toContain("Done");
expect(html).toContain("Pending");
expect(html).toContain("Regular item");
// Should have exactly 2 checkboxes
const checkboxCount = (html.match(/type="checkbox"/g) || []).length;
expect(checkboxCount).toBe(2);
});
});
describe("nested lists", () => {
it("renders nested unordered lists", () => {
const md = `- Parent\n - Child\n - Grandchild\n`;
const html = parse(md);
expect(html).toContain("Parent");
expect(html).toContain("Child");
expect(html).toContain("Grandchild");
// Should have nested <ul> elements
const ulCount = (html.match(/<ul>/g) || []).length;
expect(ulCount).toBeGreaterThanOrEqual(2);
});
it("renders nested ordered lists", () => {
const md = `1. First\n 1. Sub-first\n 2. Sub-second\n2. Second\n`;
const html = parse(md);
expect(html).toContain("<ol>");
expect(html).toContain("First");
expect(html).toContain("Sub-first");
expect(html).toContain("Second");
});
});
describe("mixed list content", () => {
it("renders code blocks inside list items", () => {
const md = [
"- Item with code:",
"",
" ```js",
" console.log(\"hi\");",
" ```",
"",
"- Normal item",
"",
].join("\n");
const html = parse(md);
expect(html).toContain("console.log(&quot;hi&quot;);");
expect(html).toContain("Normal item");
});
it("renders inline code in list items", () => {
const md = `- Use \`npm install\`\n- Run \`npm test\`\n`;
const html = parse(md);
expect(html).toContain("<code>npm install</code>");
expect(html).toContain("<code>npm test</code>");
});
it("renders bold and italic in list items", () => {
const md = `- **Bold item**\n- *Italic item*\n`;
const html = parse(md);
expect(html).toContain("<strong>Bold item</strong>");
expect(html).toContain("<em>Italic item</em>");
});
});
});