Scope DOMPurify config to call sites

Replace global setConfig with an exported sanitizeConfig passed
inline. The global config leaked into mermaid's internal
strict-mode sanitization, stripping valid SVG diagrams.
This commit is contained in:
Rūdolfs Ošiņš
2026-05-07 18:31:28 +02:00
parent faadd2906b
commit 9e268d9ac0
6 changed files with 24 additions and 13 deletions
+3 -2
View File
@@ -18,7 +18,7 @@
canonicalize,
isCommit,
} from "@app/lib/utils";
import { Renderer, markdown } from "@app/lib/markdown";
import { Renderer, markdown, sanitizeConfig } from "@app/lib/markdown";
export let content: string;
export let path: string = "/";
@@ -97,7 +97,8 @@
renderer: new Renderer($activeUnloadedRouteStore),
breaks,
}) as string,
);
sanitizeConfig,
) as string;
}
afterUpdate(async () => {
+3 -3
View File
@@ -142,10 +142,10 @@ export function renderCommitDescription(text: string): string {
out += escape(trimmed.slice(cursor));
return dompurify.sanitize(out, {
/* eslint-disable @typescript-eslint/naming-convention */
ADD_TAGS: ["radicle-external-link"],
ADD_ATTR: ["href"],
ALLOWED_TAGS: ["radicle-external-link"],
ALLOWED_ATTR: ["href"],
/* eslint-enable @typescript-eslint/naming-convention */
});
}) as string;
}
export async function loadRepoActivity(
+10 -3
View File
@@ -1,7 +1,7 @@
import type { Config } from "dompurify";
import type { MarkedExtension, Tokens } from "marked";
import type { Route } from "@app/lib/router";
import dompurify from "dompurify";
import footnoteMarkedExtension from "marked-footnote";
import katexMarkedExtension from "marked-katex-extension";
import linkifyMarkedExtension from "marked-linkify-it";
@@ -12,7 +12,14 @@ import emojis from "@app/lib/emojis";
import { canonicalize, isUrl } from "@app/lib/utils";
import { routeToPath } from "@app/lib/router";
dompurify.setConfig({
/**
* DOMPurify configuration for sanitizing markdown-derived HTML. Pass this as
* the second argument to `dompurify.sanitize` at each call site instead of
* setting it globally — a global config leaks into other consumers of the
* DOMPurify singleton (e.g. mermaid's internal strict-mode sanitization),
* which would strip the SVG output of valid diagrams.
*/
export const sanitizeConfig: Config = {
/* eslint-disable @typescript-eslint/naming-convention */
ALLOWED_ATTR: [
"align",
@@ -67,7 +74,7 @@ dompurify.setConfig({
"ul",
],
/* eslint-enable @typescript-eslint/naming-convention */
});
};
export class Renderer extends BaseRenderer {
#route: Route;
+3 -2
View File
@@ -2,7 +2,7 @@
import type { BaseUrl, Node, NodeStats } from "@http-client";
import dompurify from "dompurify";
import { markdown } from "@app/lib/markdown";
import { markdown, sanitizeConfig } from "@app/lib/markdown";
import Command from "@app/components/Command.svelte";
import Icon from "@app/components/Icon.svelte";
@@ -25,7 +25,8 @@
function render(content: string): string {
return dompurify.sanitize(
markdown({ linkify: true, emojis: true }).parse(content) as string,
);
sanitizeConfig,
) as string;
}
</script>
+3 -2
View File
@@ -2,7 +2,7 @@
import type { BaseUrl, Repo, SeedingPolicy } from "@http-client";
import dompurify from "dompurify";
import { markdown } from "@app/lib/markdown";
import { markdown, sanitizeConfig } from "@app/lib/markdown";
import { formatRepositoryId, twemoji } from "@app/lib/utils";
import Badge from "@app/components/Badge.svelte";
@@ -19,7 +19,8 @@
function render(content: string): string {
return dompurify.sanitize(
markdown({ linkify: true, emojis: true }).parseInline(content) as string,
);
sanitizeConfig,
) as string;
}
$: project = repo.payloads["xyz.radicle.project"];
@@ -1,6 +1,7 @@
<script lang="ts">
import dompurify from "dompurify";
import escape from "lodash/escape";
import { sanitizeConfig } from "@app/lib/markdown";
import { formatInlineTitle } from "@app/lib/utils";
export let content: string;
@@ -23,5 +24,5 @@
class:txt-heading-l={fontSize === "heading-l"}
class:txt-body-l-medium={fontSize === "body-l-medium"}
class:txt-body-m-regular={fontSize === "body-m-regular"}>
{@html dompurify.sanitize(formatInlineTitle(escape(content)))}
{@html dompurify.sanitize(formatInlineTitle(escape(content)), sanitizeConfig)}
</span>