fix: improve sanitation of toasts that bypass HTML escaping (#5540)

* fix: improve sanitation of toasts that bypass HTML escaping

* fix: replace absolute lib.js import with relative

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Cohee
2026-04-28 01:00:31 +03:00
committed by GitHub
parent bd9479fef8
commit 2e4ca3dabf
5 changed files with 18 additions and 14 deletions
@@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { isFalseBoolean } from './utils.js'; import { isFalseBoolean } from './utils.js';
import { DOMPurify } from '../lib.js';
/** /**
* Registers slash commands for the action loader module. * Registers slash commands for the action loader module.
@@ -275,8 +276,8 @@ export function registerActionLoaderSlashCommands() {
slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show', slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
blocking, blocking,
toastMode, toastMode,
message, message: DOMPurify.sanitize(message),
title, title: DOMPurify.sanitize(title),
stopTooltip, stopTooltip,
onStop: createClosureHandler(args.onStop), onStop: createClosureHandler(args.onStop),
onHide: createClosureHandler(args.onHide, { argName: 'onHide' }), onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),
+1 -1
View File
@@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() {
function notifyReloadCurrentChat(presetName) { function notifyReloadCurrentChat(presetName) {
toastr.info( toastr.info(
t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',
t`Preset '${presetName}' contains enabled regex scripts`, t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`,
{ {
timeOut: 5000, timeOut: 5000,
escapeHtml: false, escapeHtml: false,
+6 -4
View File
@@ -49,6 +49,7 @@ import {
uuidv4, uuidv4,
resolveAvatarData, resolveAvatarData,
findPersona, findPersona,
escapeHtml,
} from './utils.js'; } from './utils.js';
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { FILTER_TYPES, FilterHelper } from './filters.js'; import { FILTER_TYPES, FilterHelper } from './filters.js';
@@ -946,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
const temporary = getPersonaTemporaryLockInfo(); const temporary = getPersonaTemporaryLockInfo();
if (temporary.isTemporary) { if (temporary.isTemporary) {
toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, { toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {
preventDuplicates: true, onclick: () => { preventDuplicates: true,
toastr.info(temporary.info.replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false }); onclick: () => {
toastr.info(escapeHtml(temporary.info).replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false });
}, },
}); });
} }
@@ -1116,9 +1118,9 @@ async function lockPersona(type = 'chat') {
if (power_user.persona_show_notifications) { if (power_user.persona_show_notifications) {
let additional = ''; let additional = '';
if (unlinkedCharacters.length) if (unlinkedCharacters.length)
additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.join(', ')}`}`; additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`;
if (additional || !isPersonaPanelOpen()) { if (additional || !isPersonaPanelOpen()) {
toastr.success(t`User persona ${name1} is locked to character ${name2}${additional}`, t`Persona Locked`, { escapeHtml: false }); toastr.success(t`User persona ${escapeHtml(name1)} is locked to character ${escapeHtml(name2)}${additional}`, t`Persona Locked`, { escapeHtml: false });
} }
} }
} }
+5 -4
View File
@@ -16,7 +16,7 @@ import {
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js'; import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groupMembersFilter, groups, selected_group } from './group-chats.js'; import { groupCandidatesFilter, groupMembersFilter, groups, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar } from './utils.js'; import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar, escapeHtml } from './utils.js';
import { power_user } from './power-user.js'; import { power_user } from './power-user.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -977,11 +977,12 @@ async function importTags(character, { importSetting = null } = {}) {
const tagsToImport = tagNamesToImport.map(tag => getTag(tag, { createNew: true })); const tagsToImport = tagNamesToImport.map(tag => getTag(tag, { createNew: true }));
const added = addTagsToEntity(tagsToImport, character.avatar); const added = addTagsToEntity(tagsToImport, character.avatar);
const tagNames = tagsToImport.map(x => escapeHtml(x.name)).join(', ');
if (added) { if (added) {
toastr.success(t`Imported tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false }); toastr.success(t`Imported tags:` + `<br />${tagNames}`, t`Importing Tags`, { escapeHtml: false });
} else { } else {
toastr.error(t`Couldn't import tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false }); toastr.error(t`Couldn't import tags:` + `<br />${tagNames}`, t`Importing Tags`, { escapeHtml: false });
} }
return added; return added;
@@ -1124,7 +1125,7 @@ function getTag(tagName, { createNew = false } = {}) {
function createNewTag(tagName) { function createNewTag(tagName) {
const existing = getTag(tagName); const existing = getTag(tagName);
if (existing) { if (existing) {
toastr.warning(`Cannot create new tag. A tag with the name already exists:<br />${existing.name}`, 'Creating Tag', { escapeHtml: false }); toastr.warning(`Cannot create new tag. A tag with the name already exists:<br />${escapeHtml(existing.name)}`, 'Creating Tag', { escapeHtml: false });
return existing; return existing;
} }
+3 -3
View File
@@ -2487,13 +2487,13 @@ export async function checkOverwriteExistingData(type, existingNames, name, { in
return true; return true;
} }
const overwrite = interactive && await Popup.show.confirm(`${type} ${actionName}`, `<p>A ${type.toLowerCase()} with the same name already exists:<br />${existing}</p>Do you want to overwrite it?`); const overwrite = interactive && await Popup.show.confirm(`${type} ${actionName}`, `<p>A ${type.toLowerCase()} with the same name already exists:<br />${escapeHtml(existing)}</p>Do you want to overwrite it?`);
if (!overwrite) { if (!overwrite) {
toastr.warning(`${type} ${actionName.toLowerCase()} cancelled. A ${type.toLowerCase()} with the same name already exists:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false }); toastr.warning(`${type} ${actionName.toLowerCase()} cancelled. A ${type.toLowerCase()} with the same name already exists:<br />${escapeHtml(existing)}`, `${type} ${actionName}`, { escapeHtml: false });
return false; return false;
} }
toastr.info(`Overwriting Existing ${type}:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false }); toastr.info(`Overwriting Existing ${type}:<br />${escapeHtml(existing)}`, `${type} ${actionName}`, { escapeHtml: false });
// If there is an action to delete the existing data, do it, as the name might be slightly different so file name would not be the same // If there is an action to delete the existing data, do it, as the name might be slightly different so file name would not be the same
if (deleteAction) { if (deleteAction) {