From 694173e1f917ee678d7c2931f3dd129519be7b34 Mon Sep 17 00:00:00 2001 From: ! Maisto Date: Thu, 23 Jul 2026 23:02:00 +0200 Subject: [PATCH] fix(economy): prevent code injection via currency symbol/name The "Change Currency Symbol"/"Change Currency Name" modals in /economy dashboard only validated input length, then interpolated the raw value directly into src/config/bot.js's source code via string templates before writing it to disk. A value containing a double quote (e.g. `",a`, which passes the 1-3 character length check) breaks the generated JS syntax, and since bot.js is imported at process startup, this corrupts the bot's central config file and prevents it from starting on the next restart until manually repaired. Fix: - Serialize the symbol/name with JSON.stringify() instead of raw template interpolation, so quotes/backslashes are always properly escaped in the generated source. - Add an explicit character whitelist (isSafeCurrencyInput) that rejects quotes, backslashes, and backticks up front, giving users a clear validation error instead of relying solely on the escaping to hold. Co-Authored-By: Claude Sonnet 5 --- .../Economy/modules/economy_dashboard.js | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/commands/Economy/modules/economy_dashboard.js b/src/commands/Economy/modules/economy_dashboard.js index 04f507ff97..38ac4adad5 100644 --- a/src/commands/Economy/modules/economy_dashboard.js +++ b/src/commands/Economy/modules/economy_dashboard.js @@ -115,21 +115,27 @@ async function updateConfigFile(currencySymbol, currencyName) { const configPath = path.join(__dirname, '../../../config/bot.js'); let configContent = await fs.readFile(configPath, 'utf-8'); + // Serialize as proper JS string literals (escapes quotes/backslashes) instead of + // raw-interpolating user input into source code - see security review, issue #151 follow-up. + const symbolLiteral = JSON.stringify(currencySymbol); + const nameLiteral = JSON.stringify(currencyName); + const namePluralLiteral = JSON.stringify(`${currencyName}s`); + configContent = configContent.replace( /symbol:\s*"[^"]*"/, - `symbol: "${currencySymbol}"` + `symbol: ${symbolLiteral}` ); configContent = configContent.replace( /name:\s*"[^"]*",\s*\/\/\s*Currency display name/, - `name: "${currencyName}", // Currency display name` + `name: ${nameLiteral}, // Currency display name` ); configContent = configContent.replace( /namePlural:\s*"[^"]*",\s*\/\/\s*Plural display name/, - `namePlural: "${currencyName}s", // Plural display name` + `namePlural: ${namePluralLiteral}, // Plural display name` ); - + await fs.writeFile(configPath, configContent, 'utf-8'); logger.info('Config file updated successfully'); return true; @@ -139,6 +145,15 @@ async function updateConfigFile(currencySymbol, currencyName) { } } +// Only letters, numbers, currency symbols, and common punctuation - excludes quote/backslash/backtick +// so this can never contain characters that would break out of a string literal, even if a future +// refactor bypasses the JSON.stringify() call above. +const SAFE_CURRENCY_CHARS = /^[\p{L}\p{N}\p{Sc}\s.,!?-]+$/u; + +function isSafeCurrencyInput(value) { + return SAFE_CURRENCY_CHARS.test(value); +} + export default { prefixOnly: false, async execute(interaction, config, client) { @@ -453,6 +468,11 @@ async function handleChangeCurrency(selectInteraction, rootInteraction, guild) { return; } + if (!isSafeCurrencyInput(newSymbol)) { + await replyUserError(submitted, { type: ErrorTypes.VALIDATION, message: 'Currency symbol contains an unsupported character. Quotes, backslashes, and backticks are not allowed.' }); + return; + } + const success = await updateConfigFile(newSymbol, BotConfig.economy.currency.name); if (!success) { @@ -507,6 +527,11 @@ async function handleChangeName(selectInteraction, rootInteraction, guild) { return; } + if (!isSafeCurrencyInput(newName)) { + await replyUserError(submitted, { type: ErrorTypes.VALIDATION, message: 'Currency name contains an unsupported character. Quotes, backslashes, and backticks are not allowed.' }); + return; + } + const success = await updateConfigFile(BotConfig.economy.currency.symbol, newName); if (!success) {