fix(SEC-API-17): Block data: URI scheme in markdown renderer
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Remove data: from allowedSchemesByTag for img tags and add transformTags filters for both <a> and <img> elements that strip data: URI schemes (including mixed-case and whitespace-padded variants). This prevents XSS/CSRF attacks via embedded data URIs in markdown content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -146,13 +146,12 @@ plain text code
|
||||
expect(html).toContain('alt="Alt text"');
|
||||
});
|
||||
|
||||
it("should allow data URIs for images", async () => {
|
||||
it("should block data URIs for images", async () => {
|
||||
const markdown =
|
||||
"";
|
||||
const html = await renderMarkdown(markdown);
|
||||
|
||||
expect(html).toContain("<img");
|
||||
expect(html).toContain('src="data:image/png;base64');
|
||||
expect(html).not.toContain("data:");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,6 +316,45 @@ plain text code
|
||||
expect(html).not.toContain("<svg");
|
||||
expect(html).not.toContain("<script>");
|
||||
});
|
||||
|
||||
it("should block data: URI scheme in image src", async () => {
|
||||
const markdown = "";
|
||||
const html = await renderMarkdown(markdown);
|
||||
|
||||
expect(html).not.toContain("data:");
|
||||
expect(html).not.toContain("text/html");
|
||||
});
|
||||
|
||||
it("should block data: URI scheme in links", async () => {
|
||||
const markdown = "[Click me](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=)";
|
||||
const html = await renderMarkdown(markdown);
|
||||
|
||||
expect(html).not.toContain("data:");
|
||||
expect(html).not.toContain("text/html");
|
||||
});
|
||||
|
||||
it("should block data: URI with mixed case in images", async () => {
|
||||
const markdown =
|
||||
"";
|
||||
const html = await renderMarkdown(markdown);
|
||||
|
||||
expect(html).not.toContain("data:");
|
||||
expect(html).not.toContain("Data:");
|
||||
});
|
||||
|
||||
it("should block data: URI with leading whitespace", async () => {
|
||||
const markdown = "";
|
||||
const html = await renderMarkdown(markdown);
|
||||
|
||||
expect(html).not.toContain("data:");
|
||||
});
|
||||
|
||||
it("should block data: URI in sync renderer", () => {
|
||||
const markdown = "";
|
||||
const html = renderMarkdownSync(markdown);
|
||||
|
||||
expect(html).not.toContain("data:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
|
||||
@@ -107,7 +107,7 @@ const SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
|
||||
},
|
||||
allowedSchemes: ["http", "https", "mailto"],
|
||||
allowedSchemesByTag: {
|
||||
img: ["http", "https", "data"],
|
||||
img: ["http", "https"],
|
||||
},
|
||||
allowedClasses: {
|
||||
code: ["hljs", "language-*"],
|
||||
@@ -115,9 +115,18 @@ const SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
|
||||
},
|
||||
allowedIframeHostnames: [], // No iframes allowed
|
||||
// Enforce target="_blank" and rel="noopener noreferrer" for external links
|
||||
// Block data: URIs in links and images to prevent XSS/CSRF attacks
|
||||
transformTags: {
|
||||
a: (tagName: string, attribs: sanitizeHtml.Attributes) => {
|
||||
const href = attribs.href;
|
||||
// Strip data: URI scheme from links
|
||||
if (href?.trim().toLowerCase().startsWith("data:")) {
|
||||
const { href: _removed, ...safeAttribs } = attribs;
|
||||
return {
|
||||
tagName,
|
||||
attribs: safeAttribs,
|
||||
};
|
||||
}
|
||||
if (href && (href.startsWith("http://") || href.startsWith("https://"))) {
|
||||
return {
|
||||
tagName,
|
||||
@@ -133,6 +142,21 @@ const SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
|
||||
attribs,
|
||||
};
|
||||
},
|
||||
// Strip data: URI scheme from images to prevent XSS/CSRF
|
||||
img: (tagName: string, attribs: sanitizeHtml.Attributes) => {
|
||||
const src = attribs.src;
|
||||
if (src?.trim().toLowerCase().startsWith("data:")) {
|
||||
const { src: _removed, ...safeAttribs } = attribs;
|
||||
return {
|
||||
tagName,
|
||||
attribs: safeAttribs,
|
||||
};
|
||||
}
|
||||
return {
|
||||
tagName,
|
||||
attribs,
|
||||
};
|
||||
},
|
||||
// Disable task list checkboxes (make them read-only)
|
||||
input: (tagName: string, attribs: sanitizeHtml.Attributes) => {
|
||||
if (attribs.type === "checkbox") {
|
||||
|
||||
Reference in New Issue
Block a user