Add DOMPurify

This commit is contained in:
Alex Gleason 2024-01-17 12:21:19 -06:00
parent e281fbade9
commit 4aa6fdb4dd
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
7 changed files with 34 additions and 10 deletions

View File

@ -72,6 +72,7 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@tanstack/react-query": "^5.0.0",
"@types/dompurify": "^3.0.5",
"@types/escape-html": "^1.0.1",
"@types/http-link-header": "^1.0.3",
"@types/leaflet": "^1.8.0",
@ -106,6 +107,7 @@
"cryptocurrency-icons": "^0.18.1",
"cssnano": "^6.0.0",
"detect-passive-events": "^2.0.0",
"dompurify": "^3.0.8",
"dotenv": "^16.0.0",
"emoji-datasource": "14.0.0",
"emoji-mart": "^5.5.2",

View File

@ -1,3 +1,4 @@
import * as DOMPurify from 'dompurify';
import React from 'react';
import Markup from 'soapbox/components/markup';
@ -9,7 +10,7 @@ import { LogoText } from './logo-text';
const SiteBanner: React.FC = () => {
const instance = useInstance();
const description = instance.description;
const description = DOMPurify.sanitize(instance.description);
return (
<Stack space={3}>

View File

@ -1,6 +1,7 @@
/**
* Status edit normalizer
*/
*/
import * as DOMPurify from 'dompurify';
import escapeTextContentForBrowser from 'escape-html';
import {
Map as ImmutableMap,
@ -60,8 +61,8 @@ const normalizeStatusPoll = (statusEdit: ImmutableMap<string, any>) => {
const normalizeContent = (statusEdit: ImmutableMap<string, any>) => {
const emojiMap = makeEmojiMap(statusEdit.get('emojis'));
const contentHtml = stripCompatibilityFeatures(emojify(statusEdit.get('content'), emojiMap));
const spoilerHtml = emojify(escapeTextContentForBrowser(statusEdit.get('spoiler_text')), emojiMap);
const contentHtml = DOMPurify.sanitize(stripCompatibilityFeatures(emojify(statusEdit.get('content'), emojiMap)), { ADD_ATTR: ['target'] });
const spoilerHtml = DOMPurify.sanitize(emojify(escapeTextContentForBrowser(statusEdit.get('spoiler_text')), emojiMap), { ADD_ATTR: ['target'] });
return statusEdit
.set('contentHtml', contentHtml)

View File

@ -1,3 +1,4 @@
import * as DOMPurify from 'dompurify';
import escapeTextContentForBrowser from 'escape-html';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
@ -117,8 +118,8 @@ export const calculateStatus = (
return status.merge({
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '',
contentHtml: stripCompatibilityFeatures(emojify(status.content, emojiMap)),
spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap),
contentHtml: DOMPurify.sanitize(stripCompatibilityFeatures(emojify(status.content, emojiMap)), { USE_PROFILES: { html: true } }),
spoilerHtml: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(spoilerText), emojiMap), { USE_PROFILES: { html: true } }),
hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive,
});
}

View File

@ -1,3 +1,4 @@
import * as DOMPurify from 'dompurify';
import escapeTextContentForBrowser from 'escape-html';
import z from 'zod';
@ -112,7 +113,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
const newFields = fields.map((field) => ({
...field,
name_emojified: emojify(escapeTextContentForBrowser(field.name), customEmojiMap),
name_emojified: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(field.name), customEmojiMap), { USE_PROFILES: { html: true } }),
value_emojified: emojify(field.value, customEmojiMap),
value_plain: unescapeHTML(field.value),
}));
@ -130,7 +131,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
avatar_static: account.avatar_static || account.avatar,
discoverable: account.discoverable || account.source?.pleroma?.discoverable || false,
display_name: displayName,
display_name_html: emojify(escapeTextContentForBrowser(displayName), customEmojiMap),
display_name_html: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(displayName), customEmojiMap), { USE_PROFILES: { html: true } }),
domain,
fields: newFields,
fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`),
@ -138,7 +139,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
moderator: pleroma?.is_moderator || false,
local: pleroma?.is_local !== undefined ? pleroma.is_local : account.acct.split('@')[1] === undefined,
location: account.location || pleroma?.location || other_settings?.location || '',
note_emojified: emojify(account.note, customEmojiMap),
note_emojified: DOMPurify.sanitize(emojify(account.note, customEmojiMap), { USE_PROFILES: { html: true } }),
pleroma: (() => {
if (!pleroma) return undefined;
const { relationship, ...rest } = pleroma;

View File

@ -1,3 +1,4 @@
import * as DOMPurify from 'dompurify';
import escapeTextContentForBrowser from 'escape-html';
import { z } from 'zod';
@ -30,7 +31,7 @@ const pollSchema = z.object({
const emojifiedOptions = poll.options.map((option) => ({
...option,
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
title_emojified: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(option.title), emojiMap), { ALLOWED_TAGS: [] }),
}));
// If the user has votes, they have certainly voted.

View File

@ -2375,6 +2375,13 @@
"@types/node" "*"
"@types/responselike" "^1.0.0"
"@types/dompurify@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7"
integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==
dependencies:
"@types/trusted-types" "*"
"@types/escape-html@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.1.tgz#b19b4646915f0ae2c306bf984dc0a59c5cfc97ba"
@ -2612,6 +2619,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564"
integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==
"@types/trusted-types@*":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
"@types/trusted-types@^2.0.2":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
@ -4096,6 +4108,11 @@ domhandler@^4.2.0, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
dompurify@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.8.tgz#e0021ab1b09184bc8af7e35c7dd9063f43a8a437"
integrity sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ==
domutils@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"