Repository: cloudflare/wildebeest
File: backend/src/activitypub/objects/index.ts (TypeScript)

Code before:
	if ('name' in properties) {
		sanitized.name = await sanitizeName(properties.name as string)
	}
	return sanitized
}

/**
 * Sanitizes the given string as ActivityPub Object content.
 *
 * This sanitization follows that of Mastodon
 *  - convert all elements to `<p>` unless they are recognized as one of `<p>`, `<span>`, `<br>` or `<a>`.
 *  - remove all CSS classes that are not micro-formats or semantic.
 *
 * See https://docs.joinmastodon.org/spec/activitypub/#sanitization
 */
export async function sanitizeContent(unsafeContent: string): Promise<string> {
	return await contentRewriter.transform(new Response(unsafeContent)).text()
}

/**
 * Sanitizes given string as an ActivityPub Object name.
 *
 * This sanitization removes all HTML elements from the string leaving only the text content.
 */
export async function sanitizeName(dirty: string): Promise<string> {
	return await nameRewriter.transform(new Response(dirty)).text()
}

const contentRewriter = new HTMLRewriter()
contentRewriter.on('*', {
	element(el) {
		if (!['p', 'span', 'br', 'a'].includes(el.tagName)) {
			el.tagName = 'p'
		}

		if (el.hasAttribute('class')) {
			const classes = el.getAttribute('class')!.split(' ')
			const sanitizedClasses = classes.filter((c) =>
				/^(h|p|u|dt|e)-|^mention$|^hashtag$|^ellipsis$|^invisible$/.test(c)
			)
			el.setAttribute('class', sanitizedClasses.join(' '))
		}
	},
})

const nameRewriter = new HTMLRewriter()
nameRewriter.on('*', {
	element(el) {
		el.removeAndKeepContent()
	},
})

Review comment (security):
```suggestion
export async function sanitizeName(unsafeName: string): Promise<string> {
	return await nameRewriter.transform(new Response(unsafeName)).text()
```
(for consistency with the `sanitizeContent` function)

Code after:
	if ('name' in properties) {
		sanitized.name = await sanitizeName(properties.name as string)
	}
	return sanitized
}

/**
 * Sanitizes the given string as ActivityPub Object content.
 *
 * This sanitization follows that of Mastodon
 *  - convert all elements to `<p>` unless they are recognized as one of `<p>`, `<span>`, `<br>` or `<a>`.
 *  - remove all CSS classes that are not micro-formats or semantic.
 *
 * See https://docs.joinmastodon.org/spec/activitypub/#sanitization
 */
export async function sanitizeContent(unsafeContent: string): Promise<string> {
	return await contentRewriter.transform(new Response(unsafeContent)).text()
}

/**
 * Sanitizes given string as an ActivityPub Object name.
 *
 * This sanitization removes all HTML elements from the string leaving only the text content.
 */
export async function sanitizeName(unsafeName: string): Promise<string> {
	return await nameRewriter.transform(new Response(unsafeName)).text()
}

const contentRewriter = new HTMLRewriter()
contentRewriter.on('*', {
	element(el) {
		if (!['p', 'span', 'br', 'a'].includes(el.tagName)) {
			el.tagName = 'p'
		}

		if (el.hasAttribute('class')) {
			const classes = el.getAttribute('class')!.split(/\s+/)
			const sanitizedClasses = classes.filter((c) =>
				/^(h|p|u|dt|e)-|^mention$|^hashtag$|^ellipsis$|^invisible$/.test(c)
			)
			el.setAttribute('class', sanitizedClasses.join(' '))
		}
	},
})

const nameRewriter = new HTMLRewriter()
nameRewriter.on('*', {
	element(el) {
		el.removeAndKeepContent()
	},
})