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>

2e4ca3dabf6503451587f1c28387e742e08ea60c

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
5 files changed, +18 -14Showing whitespace changes
public/scripts/action-loader-slashcommands.js+3 -2
@@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
7import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';7import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
8import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';8import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
9import { isFalseBoolean } from './utils.js';9import { isFalseBoolean } from './utils.js';
10import { DOMPurify } from '../lib.js';
1011
11/**12/**
12 * Registers slash commands for the action loader module.13 * Registers slash commands for the action loader module.
@@ -275,8 +276,8 @@ export function registerActionLoaderSlashCommands() {
275 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',276 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
276 blocking,277 blocking,
277 toastMode,278 toastMode,
278 message,279 message: DOMPurify.sanitize(message),
279 title,280 title: DOMPurify.sanitize(title),
280 stopTooltip,281 stopTooltip,
281 onStop: createClosureHandler(args.onStop),282 onStop: createClosureHandler(args.onStop),
282 onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),283 onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),
public/scripts/extensions/regex/index.js+1 -1
@@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() {
1639function notifyReloadCurrentChat(presetName) {1639function notifyReloadCurrentChat(presetName) {
1640 toastr.info(1640 toastr.info(
1641 t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',1641 t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',
1642 t`Preset '${presetName}' contains enabled regex scripts`,1642 t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`,
1643 {1643 {
1644 timeOut: 5000,1644 timeOut: 5000,
1645 escapeHtml: false,1645 escapeHtml: false,
public/scripts/personas.js+6 -4
@@ -49,6 +49,7 @@ import {
49 uuidv4,49 uuidv4,
50 resolveAvatarData,50 resolveAvatarData,
51 findPersona,51 findPersona,
52 escapeHtml,
52} from './utils.js';53} from './utils.js';
53import { debounce_timeout } from './constants.js';54import { debounce_timeout } from './constants.js';
54import { FILTER_TYPES, FilterHelper } from './filters.js';55import { FILTER_TYPES, FilterHelper } from './filters.js';
@@ -946,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
946 const temporary = getPersonaTemporaryLockInfo();947 const temporary = getPersonaTemporaryLockInfo();
947 if (temporary.isTemporary) {948 if (temporary.isTemporary) {
948 toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {949 toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {
949 preventDuplicates: true, onclick: () => {950 preventDuplicates: true,
950 toastr.info(temporary.info.replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false });951 onclick: () => {
952 toastr.info(escapeHtml(temporary.info).replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false });
951 },953 },
952 });954 });
953 }955 }
@@ -1116,9 +1118,9 @@ async function lockPersona(type = 'chat') {
1116 if (power_user.persona_show_notifications) {1118 if (power_user.persona_show_notifications) {
1117 let additional = '';1119 let additional = '';
1118 if (unlinkedCharacters.length)1120 if (unlinkedCharacters.length)
1119 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.join(', ')}`}`;1121 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`;
1120 if (additional || !isPersonaPanelOpen()) {1122 if (additional || !isPersonaPanelOpen()) {
1121 toastr.success(t`User persona ${name1} is locked to character ${name2}${additional}`, t`Persona Locked`, { escapeHtml: false });1123 toastr.success(t`User persona ${escapeHtml(name1)} is locked to character ${escapeHtml(name2)}${additional}`, t`Persona Locked`, { escapeHtml: false });
1122 }1124 }
1123 }1125 }
1124 }1126 }
public/scripts/tags.js+5 -4
@@ -16,7 +16,7 @@ import {
16import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';16import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1717
18import { groupCandidatesFilter, groupMembersFilter, groups, selected_group } from './group-chats.js';18import { groupCandidatesFilter, groupMembersFilter, groups, selected_group } from './group-chats.js';
19import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar } from './utils.js';19import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar, escapeHtml } from './utils.js';
20import { power_user } from './power-user.js';20import { power_user } from './power-user.js';
21import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';21import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
22import { SlashCommand } from './slash-commands/SlashCommand.js';22import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -977,11 +977,12 @@ async function importTags(character, { importSetting = null } = {}) {
977977
978 const tagsToImport = tagNamesToImport.map(tag => getTag(tag, { createNew: true }));978 const tagsToImport = tagNamesToImport.map(tag => getTag(tag, { createNew: true }));
979 const added = addTagsToEntity(tagsToImport, character.avatar);979 const added = addTagsToEntity(tagsToImport, character.avatar);
980 const tagNames = tagsToImport.map(x => escapeHtml(x.name)).join(', ');
980981
981 if (added) {982 if (added) {
982 toastr.success(t`Imported tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false });983 toastr.success(t`Imported tags:` + `<br />${tagNames}`, t`Importing Tags`, { escapeHtml: false });
983 } else {984 } else {
984 toastr.error(t`Couldn't import tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false });985 toastr.error(t`Couldn't import tags:` + `<br />${tagNames}`, t`Importing Tags`, { escapeHtml: false });
985 }986 }
986987
987 return added;988 return added;
@@ -1124,7 +1125,7 @@ function getTag(tagName, { createNew = false } = {}) {
1124function createNewTag(tagName) {1125function createNewTag(tagName) {
1125 const existing = getTag(tagName);1126 const existing = getTag(tagName);
1126 if (existing) {1127 if (existing) {
1127 toastr.warning(`Cannot create new tag. A tag with the name already exists:<br />${existing.name}`, 'Creating Tag', { escapeHtml: false });1128 toastr.warning(`Cannot create new tag. A tag with the name already exists:<br />${escapeHtml(existing.name)}`, 'Creating Tag', { escapeHtml: false });
1128 return existing;1129 return existing;
1129 }1130 }
11301131
public/scripts/utils.js+3 -3
@@ -2487,13 +2487,13 @@ export async function checkOverwriteExistingData(type, existingNames, name, { in
2487 return true;2487 return true;
2488 }2488 }
24892489
2490 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?`);2490 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?`);
2491 if (!overwrite) {2491 if (!overwrite) {
2492 toastr.warning(`${type} ${actionName.toLowerCase()} cancelled. A ${type.toLowerCase()} with the same name already exists:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false });2492 toastr.warning(`${type} ${actionName.toLowerCase()} cancelled. A ${type.toLowerCase()} with the same name already exists:<br />${escapeHtml(existing)}`, `${type} ${actionName}`, { escapeHtml: false });
2493 return false;2493 return false;
2494 }2494 }
24952495
2496 toastr.info(`Overwriting Existing ${type}:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false });2496 toastr.info(`Overwriting Existing ${type}:<br />${escapeHtml(existing)}`, `${type} ${actionName}`, { escapeHtml: false });
24972497
2498 // 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 same2498 // 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
2499 if (deleteAction) {2499 if (deleteAction) {