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