mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-07-16 10:07:20 +02:00
Replace isomorphic-dompurify with sanitize-html for Node 21 compat (#663)
This commit is contained in:
Generated
+359
-4155
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -50,7 +50,6 @@
|
|||||||
"express-slow-down": "^2.0.1",
|
"express-slow-down": "^2.0.1",
|
||||||
"got": "^11.8.6",
|
"got": "^11.8.6",
|
||||||
"inquirer": "^8.2.6",
|
"inquirer": "^8.2.6",
|
||||||
"isomorphic-dompurify": "^3.8.0",
|
|
||||||
"istextorbinary": "^9.5.0",
|
"istextorbinary": "^9.5.0",
|
||||||
"marked": "^5.1.2",
|
"marked": "^5.1.2",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
@@ -61,6 +60,7 @@
|
|||||||
"passport-github2": "^0.1.12",
|
"passport-github2": "^0.1.12",
|
||||||
"rate-limit-redis": "^4.2.0",
|
"rate-limit-redis": "^4.2.0",
|
||||||
"redis": "^4.6.13",
|
"redis": "^4.6.13",
|
||||||
|
"sanitize-html": "^2.17.2",
|
||||||
"ts-custom-error": "^3.3.1",
|
"ts-custom-error": "^3.3.1",
|
||||||
"unzip-stream": "^0.3.1"
|
"unzip-stream": "^0.3.1"
|
||||||
},
|
},
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
"@types/parse-github-url": "^1.0.3",
|
"@types/parse-github-url": "^1.0.3",
|
||||||
"@types/passport": "^1.0.16",
|
"@types/passport": "^1.0.16",
|
||||||
"@types/passport-github2": "^1.2.9",
|
"@types/passport-github2": "^1.2.9",
|
||||||
|
"@types/sanitize-html": "^2.16.1",
|
||||||
"@types/unzip-stream": "^0.3.4",
|
"@types/unzip-stream": "^0.3.4",
|
||||||
"chai": "^4.5.0",
|
"chai": "^4.5.0",
|
||||||
"eslint": "^10.2.0",
|
"eslint": "^10.2.0",
|
||||||
|
|||||||
@@ -4,10 +4,37 @@ import * as path from "path";
|
|||||||
import AnonymizedFile from "../../core/AnonymizedFile";
|
import AnonymizedFile from "../../core/AnonymizedFile";
|
||||||
import AnonymousError from "../../core/AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import * as marked from "marked";
|
import * as marked from "marked";
|
||||||
import DOMPurify from "isomorphic-dompurify";
|
import * as sanitizeHtml from "sanitize-html";
|
||||||
import { streamToString } from "../../core/anonymize-utils";
|
import { streamToString } from "../../core/anonymize-utils";
|
||||||
import { IFile } from "../../core/model/files/files.types";
|
import { IFile } from "../../core/model/files/files.types";
|
||||||
|
|
||||||
|
const sanitizeOptions: sanitizeHtml.IOptions = {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat([
|
||||||
|
"img",
|
||||||
|
"video",
|
||||||
|
"input",
|
||||||
|
"details",
|
||||||
|
"summary",
|
||||||
|
"del",
|
||||||
|
"ins",
|
||||||
|
"sup",
|
||||||
|
"sub",
|
||||||
|
]),
|
||||||
|
allowedAttributes: {
|
||||||
|
...sanitizeHtml.defaults.allowedAttributes,
|
||||||
|
img: ["src", "srcset", "alt", "title", "width", "height", "loading"],
|
||||||
|
video: ["src", "controls", "title"],
|
||||||
|
input: ["type", "checked", "disabled"],
|
||||||
|
code: ["class"],
|
||||||
|
span: ["class"],
|
||||||
|
div: ["class"],
|
||||||
|
pre: ["class"],
|
||||||
|
td: ["align"],
|
||||||
|
th: ["align"],
|
||||||
|
},
|
||||||
|
allowedSchemes: ["http", "https", "mailto"],
|
||||||
|
};
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const indexPriority = [
|
const indexPriority = [
|
||||||
@@ -114,7 +141,7 @@ async function webView(req: express.Request, res: express.Response) {
|
|||||||
}
|
}
|
||||||
if (f.extension() == "md") {
|
if (f.extension() == "md") {
|
||||||
const content = await streamToString(await f.anonymizedContent());
|
const content = await streamToString(await f.anonymizedContent());
|
||||||
const body = DOMPurify.sanitize(marked.marked(content, { headerIds: false, mangle: false }));
|
const body = sanitizeHtml(marked.marked(content, { headerIds: false, mangle: false }), sanitizeOptions);
|
||||||
const html = `<!DOCTYPE html><html><head><title>Content</title></head><link rel="stylesheet" href="/css/all.min.css" /><body><div class="container p-3 file-content markdown-body">${body}<div></body></html>`;
|
const html = `<!DOCTYPE html><html><head><title>Content</title></head><link rel="stylesheet" href="/css/all.min.css" /><body><div class="container p-3 file-content markdown-body">${body}<div></body></html>`;
|
||||||
res.contentType("text/html").send(html);
|
res.contentType("text/html").send(html);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,14 +1,41 @@
|
|||||||
const { expect } = require("chai");
|
const { expect } = require("chai");
|
||||||
const { marked } = require("marked");
|
const { marked } = require("marked");
|
||||||
const DOMPurify = require("isomorphic-dompurify");
|
const sanitizeHtml = require("sanitize-html");
|
||||||
|
|
||||||
|
const sanitizeOptions = {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat([
|
||||||
|
"img",
|
||||||
|
"video",
|
||||||
|
"input",
|
||||||
|
"details",
|
||||||
|
"summary",
|
||||||
|
"del",
|
||||||
|
"ins",
|
||||||
|
"sup",
|
||||||
|
"sub",
|
||||||
|
]),
|
||||||
|
allowedAttributes: {
|
||||||
|
...sanitizeHtml.defaults.allowedAttributes,
|
||||||
|
img: ["src", "srcset", "alt", "title", "width", "height", "loading"],
|
||||||
|
video: ["src", "controls", "title"],
|
||||||
|
input: ["type", "checked", "disabled"],
|
||||||
|
code: ["class"],
|
||||||
|
span: ["class"],
|
||||||
|
div: ["class"],
|
||||||
|
pre: ["class"],
|
||||||
|
td: ["align"],
|
||||||
|
th: ["align"],
|
||||||
|
},
|
||||||
|
allowedSchemes: ["http", "https", "mailto"],
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper that mirrors the server-side rendering pipeline in webview.ts:
|
* Mirrors the server-side rendering pipeline in webview.ts:
|
||||||
* DOMPurify.sanitize(marked.marked(content, { headerIds: false, mangle: false }))
|
* sanitizeHtml(marked(content, opts), sanitizeOptions)
|
||||||
*/
|
*/
|
||||||
function renderAndSanitize(markdown) {
|
function renderAndSanitize(markdown) {
|
||||||
const raw = marked(markdown, { headerIds: false, mangle: false });
|
const raw = marked(markdown, { headerIds: false, mangle: false });
|
||||||
return DOMPurify.sanitize(raw);
|
return sanitizeHtml(raw, sanitizeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Markdown sanitization", function () {
|
describe("Markdown sanitization", function () {
|
||||||
@@ -110,11 +137,11 @@ describe("Markdown sanitization", function () {
|
|||||||
expect(html).to.not.include("<embed");
|
expect(html).to.not.include("<embed");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("strips form action with javascript: URL", function () {
|
it("strips form tag", function () {
|
||||||
const html = renderAndSanitize(
|
const html = renderAndSanitize(
|
||||||
'<form action="javascript:alert(1)"><input type="submit"></form>'
|
'<form action="https://evil.com/steal"><input type="text" name="password"></form>'
|
||||||
);
|
);
|
||||||
expect(html).to.not.include("javascript:");
|
expect(html).to.not.include("<form");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user